mirror of
https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git
synced 2024-11-26 12:51:30 +00:00
340 lines
12 KiB
Nix
340 lines
12 KiB
Nix
|
{ config, pkgs, lib, ... }:
|
||
|
let
|
||
|
cfg = config.selfprivacy.ldap;
|
||
|
domain = lib.concatMapStringsSep "," (x: "dc=${x}") (lib.splitString "." cfg.domain);
|
||
|
openssh-ldap-publickey = pkgs.fetchFromGitHub {
|
||
|
owner = "AndriiGrytsenko";
|
||
|
repo = "openssh-ldap-publickey";
|
||
|
rev = "v1.0.2";
|
||
|
hash = "sha256-Citukp6dQrmFUGFTRSXAhoUpjKUlEvkAOffx2/P5Gag=";
|
||
|
};
|
||
|
in
|
||
|
{
|
||
|
options = {
|
||
|
selfprivacy.ldap = {
|
||
|
enable = lib.mkEnableOption (lib.mdDoc "LDAP integration");
|
||
|
domain = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
example = "example.com";
|
||
|
description = ''
|
||
|
LDAP domain.
|
||
|
'';
|
||
|
};
|
||
|
rootUser = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
default = "root";
|
||
|
description = lib.mdDoc ''
|
||
|
LDAP root user.
|
||
|
'';
|
||
|
};
|
||
|
rootHashedPassword = lib.mkOption {
|
||
|
type = lib.types.passwdEntry lib.types.str;
|
||
|
description = lib.mdDoc ''
|
||
|
LDAP root user hashed password.
|
||
|
'';
|
||
|
};
|
||
|
users = lib.mkOption {
|
||
|
type = lib.types.listOf (lib.types.submodule {
|
||
|
options = {
|
||
|
username = lib.mkOption {
|
||
|
type = lib.types.passwdEntry lib.types.str;
|
||
|
example = "john";
|
||
|
description = lib.mdDoc ''
|
||
|
User's username.
|
||
|
'';
|
||
|
};
|
||
|
hashedPassword = lib.mkOption {
|
||
|
type = lib.types.passwdEntry lib.types.str;
|
||
|
description = lib.mdDoc ''
|
||
|
Specifies the hashed password for the user.
|
||
|
'';
|
||
|
};
|
||
|
sshKeys = lib.mkOption {
|
||
|
type = lib.types.listOf lib.types.singleLineStr;
|
||
|
default = [ ];
|
||
|
description = lib.mdDoc ''
|
||
|
A list of user's OpenSSH public keys.
|
||
|
'';
|
||
|
};
|
||
|
email = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
example = "john@example.com";
|
||
|
description = lib.mdDoc ''
|
||
|
User email for LDAP.
|
||
|
'';
|
||
|
};
|
||
|
displayName = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
example = "John Doe";
|
||
|
default = "";
|
||
|
description = lib.mdDoc ''
|
||
|
Display name for LDAP.
|
||
|
'';
|
||
|
};
|
||
|
firstName = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
example = "John";
|
||
|
default = "";
|
||
|
description = lib.mdDoc ''
|
||
|
User's first name for LDAP.
|
||
|
'';
|
||
|
};
|
||
|
lastName = lib.mkOption {
|
||
|
type = lib.types.str;
|
||
|
example = "Doe";
|
||
|
default = "";
|
||
|
description = lib.mdDoc ''
|
||
|
User's last name for LDAP.
|
||
|
'';
|
||
|
};
|
||
|
jpegPhoto = lib.mkOption {
|
||
|
type = lib.types.nullOr lib.types.singleLineStr;
|
||
|
default = null;
|
||
|
description = lib.mdDoc ''
|
||
|
A jpegPhoto attribute for LDAP, base64-encoded.
|
||
|
'';
|
||
|
};
|
||
|
groups = lib.mkOption {
|
||
|
type = lib.types.listOf (lib.types.enum [
|
||
|
"admin"
|
||
|
"gitea"
|
||
|
"nextcloud"
|
||
|
"pleroma"
|
||
|
]);
|
||
|
example = [ "gitea" ];
|
||
|
default = [ ];
|
||
|
description = lib.mdDoc ''
|
||
|
Which services the user is allowed to use.
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
});
|
||
|
default = [ ];
|
||
|
description = lib.mdDoc ''
|
||
|
List of LDAP users.
|
||
|
'';
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
config = lib.mkMerge [
|
||
|
(lib.mkIf cfg.enable {
|
||
|
services.openldap =
|
||
|
let
|
||
|
filterUsers = group: users: lib.filter
|
||
|
(user: builtins.elem group user.groups)
|
||
|
users;
|
||
|
mkUser = usersNamespace: user: lib.concatStringsSep "\n" ([
|
||
|
"dn: uid=${user.username},ou=${usersNamespace},${domain}"
|
||
|
"objectClass: inetOrgPerson"
|
||
|
"objectClass: shadowAccount"
|
||
|
] ++ lib.optionals (user.sshKeys != [ ]) [
|
||
|
"objectClass: ldapPublicKey"
|
||
|
] ++ lib.optionals (user.jpegPhoto != null) [
|
||
|
"jpegPhoto:: ${user.jpegPhoto}"
|
||
|
] ++ (map (key: "sshPublicKey: ${key}") user.sshKeys)
|
||
|
++ [
|
||
|
"mail: ${user.email}"
|
||
|
"displayName: ${user.displayName}"
|
||
|
"cn: ${user.firstName}"
|
||
|
"sn: ${user.lastName}"
|
||
|
"userPassword: {crypt}${user.hashedPassword}"
|
||
|
]);
|
||
|
mkGroup = usersNamespace: users: group:
|
||
|
let
|
||
|
groupUsers = (filterUsers group users);
|
||
|
in
|
||
|
lib.optionalString (groupUsers != [ ]) ''
|
||
|
dn: cn=${group},ou=groups,${domain}
|
||
|
objectClass: groupOfNames
|
||
|
${lib.concatMapStringsSep
|
||
|
"\n"
|
||
|
(user: "member: uid=${user.username},ou=${usersNamespace},${domain}")
|
||
|
groupUsers
|
||
|
}
|
||
|
'';
|
||
|
mkUsersNamespace = usersNamespace: users: ''
|
||
|
dn: ou=${usersNamespace},${domain}
|
||
|
objectClass: organizationalUnit
|
||
|
|
||
|
${lib.concatMapStringsSep "\n\n" (mkUser usersNamespace) users}
|
||
|
'';
|
||
|
mkGroupsNamespace = usersNamespace: users: groupsNamespace: groups: ''
|
||
|
dn: ou=${groupsNamespace},${domain}
|
||
|
objectClass: organizationalUnit
|
||
|
|
||
|
${lib.concatMapStringsSep "\n\n" (mkGroup usersNamespace users) groups}
|
||
|
'';
|
||
|
in
|
||
|
{
|
||
|
enable = true;
|
||
|
urlList = [ "ldap://localhost:389" ];
|
||
|
declarativeContents."${domain}" = ''
|
||
|
dn: ${domain}
|
||
|
objectClass: domain
|
||
|
|
||
|
${mkUsersNamespace "users" cfg.users}
|
||
|
|
||
|
${mkGroupsNamespace "users" cfg.users "groups" [
|
||
|
"admin"
|
||
|
"gitea"
|
||
|
"nextcloud"
|
||
|
]}
|
||
|
|
||
|
# pleroma has no support for ldap filters
|
||
|
# so we just put pleroma users under separate namespace
|
||
|
# https://git.pleroma.social/pleroma/pleroma/-/issues/1645
|
||
|
${mkUsersNamespace "pleroma" (filterUsers "pleroma" cfg.users)}
|
||
|
'';
|
||
|
settings = {
|
||
|
children = {
|
||
|
"cn=schema".includes = [
|
||
|
"${pkgs.openldap}/etc/schema/core.ldif"
|
||
|
"${pkgs.openldap}/etc/schema/cosine.ldif"
|
||
|
"${pkgs.openldap}/etc/schema/dyngroup.ldif"
|
||
|
"${pkgs.openldap}/etc/schema/inetorgperson.ldif"
|
||
|
"${pkgs.openldap}/etc/schema/nis.ldif"
|
||
|
"${openssh-ldap-publickey}/misc/openssh-lpk-openldap.ldif"
|
||
|
];
|
||
|
"cn=modules" = {
|
||
|
attrs = {
|
||
|
objectClass = [ "olcModuleList" ];
|
||
|
olcModuleLoad = "dynlist";
|
||
|
};
|
||
|
};
|
||
|
"olcDatabase={0}config" = {
|
||
|
attrs = {
|
||
|
objectClass = [ "olcDatabaseConfig" ];
|
||
|
olcDatabase = "{0}config";
|
||
|
olcRootDN = "cn=${cfg.rootUser},cn=config";
|
||
|
olcRootPW = "{crypt}${cfg.rootHashedPassword}";
|
||
|
};
|
||
|
};
|
||
|
"olcDatabase={1}mdb" = {
|
||
|
attrs = {
|
||
|
objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
|
||
|
olcDatabase = "{1}mdb";
|
||
|
olcDbDirectory = "/var/lib/openldap/db";
|
||
|
olcSuffix = "${domain}";
|
||
|
olcRootDN = "cn=${cfg.rootUser},${domain}";
|
||
|
olcRootPW = "{crypt}${cfg.rootHashedPassword}";
|
||
|
};
|
||
|
};
|
||
|
"olcOverlay=dynlist,olcDatabase={1}mdb" = {
|
||
|
attrs = {
|
||
|
objectClass = [ "olcOverlayConfig" "olcDynListConfig" ];
|
||
|
olcDynListAttrSet = "groupOfURLs memberURL member+memberOf@groupOfNames";
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
};
|
||
|
})
|
||
|
(lib.mkIf config.services.gitea.enable {
|
||
|
systemd.services.gitea.preStart = lib.mkAfter ''
|
||
|
ldap_id=$(${config.services.gitea.package}/bin/gitea admin auth list | grep nixos-ldap | ${pkgs.gawk}/bin/awk '{ print $1 }' || true)
|
||
|
|
||
|
${lib.optionalString (!cfg.enable) ''
|
||
|
if [ ! -z "$ldap_id" ]; then
|
||
|
${config.services.gitea.package}/bin/gitea admin auth update-ldap \
|
||
|
--id $ldap_id \
|
||
|
--not-active
|
||
|
fi
|
||
|
''}
|
||
|
|
||
|
${lib.optionalString cfg.enable ''
|
||
|
if [ -z "$ldap_id" ]; then
|
||
|
auth_command="add-ldap"
|
||
|
else
|
||
|
auth_command="update-ldap --id $ldap_id"
|
||
|
fi
|
||
|
|
||
|
# https://docs.gitea.io/en-us/command-line/#admin
|
||
|
${config.services.gitea.package}/bin/gitea admin auth $auth_command \
|
||
|
--id $ldap_id \
|
||
|
--name nixos-ldap \
|
||
|
--security-protocol unencrypted \
|
||
|
--host 127.0.0.1 \
|
||
|
--port 389 \
|
||
|
--bind-dn "${domain}" \
|
||
|
--user-search-base "ou=users,${domain}" \
|
||
|
--user-filter "(&(objectClass=shadowAccount)(memberOf=cn=gitea,ou=groups,${domain})(uid=%s))" \
|
||
|
--admin-filter "(&(objectClass=shadowAccount)(memberOf=cn=admin,ou=groups,${domain}))" \
|
||
|
--username-attribute uid \
|
||
|
--email-attribute mail \
|
||
|
--firstname-attribute cn \
|
||
|
--surname-attribute sn \
|
||
|
--avatar-attribute jpegPhoto \
|
||
|
--public-ssh-key-attribute sshPublicKey \
|
||
|
--synchronize-users
|
||
|
''}
|
||
|
'';
|
||
|
})
|
||
|
(lib.mkIf config.services.nextcloud.enable {
|
||
|
# No support for admins via LDAP yet:
|
||
|
# https://github.com/nextcloud/server/issues/6428
|
||
|
systemd.services.nextcloud-setup.script =
|
||
|
let
|
||
|
# https://docs.nextcloud.com/server/25/admin_manual/configuration_server/occ_command.html#ldap-commands
|
||
|
# https://docs.nextcloud.com/server/25/admin_manual/configuration_user/user_auth_ldap_api.html#configuration-keys
|
||
|
occAction = action: "nextcloud-occ --no-interaction ${action}";
|
||
|
ldapAction = action: occAction "ldap:${action}";
|
||
|
ldapConfigIdFile = lib.escapeShellArg "${config.services.nextcloud.datadir}/config/.ldap-nixos-config-id";
|
||
|
ldapConfigAction = action: "${ldapAction action} $(<${ldapConfigIdFile})";
|
||
|
ldapSetConfig = ldapConfigAction "set-config";
|
||
|
ldapTestConfig = ldapConfigAction "test-config";
|
||
|
in
|
||
|
lib.mkAfter ''
|
||
|
${lib.optionalString (!cfg.enable) ''
|
||
|
if [ -f ${ldapConfigIdFile} ]; then
|
||
|
${ldapSetConfig} ldapConfigurationActive 0
|
||
|
fi
|
||
|
''}
|
||
|
|
||
|
${lib.optionalString cfg.enable ''
|
||
|
if [ ! -f ${ldapConfigIdFile} ]; then
|
||
|
${occAction "app:enable"} user_ldap
|
||
|
if ! ${ldapAction "create-empty-config"} --only-print-prefix > ${ldapConfigIdFile}; then
|
||
|
rm ${ldapConfigIdFile}
|
||
|
echo "Failed to create LDAP configuration"
|
||
|
exit 1
|
||
|
fi
|
||
|
fi
|
||
|
|
||
|
${ldapSetConfig} ldapHost 127.0.0.1
|
||
|
${ldapSetConfig} ldapPort 389
|
||
|
${ldapSetConfig} ldapBase "${domain}"
|
||
|
${ldapSetConfig} ldapBaseUsers "ou=users,${domain}"
|
||
|
${ldapSetConfig} ldapUserFilter "(&(objectClass=shadowAccount)(memberOf=cn=nextcloud,ou=groups,${domain}))"
|
||
|
${ldapSetConfig} ldapLoginFilter "(&(objectClass=shadowAccount)(memberOf=cn=nextcloud,ou=groups,${domain})(uid=%uid))"
|
||
|
${ldapSetConfig} ldapExpertUsernameAttr uid
|
||
|
${ldapSetConfig} ldapEmailAttribute mail
|
||
|
${ldapSetConfig} ldapUserDisplayName displayName
|
||
|
${ldapSetConfig} ldapUserAvatarRule "data:jpegPhoto"
|
||
|
|
||
|
if ${ldapTestConfig} | grep -q 'configuration is valid and the connection could be established'; then
|
||
|
${ldapSetConfig} ldapConfigurationActive 1
|
||
|
else
|
||
|
echo "LDAP configuration is invalid, disabling"
|
||
|
${ldapSetConfig} ldapConfigurationActive 0
|
||
|
fi
|
||
|
''}
|
||
|
'';
|
||
|
})
|
||
|
(lib.mkIf (config.services.pleroma.enable && cfg.enable) {
|
||
|
services.pleroma.configs = [
|
||
|
''
|
||
|
import Config
|
||
|
config :pleroma, Pleroma.Web.Auth.Authenticator, Pleroma.Web.Auth.LDAPAuthenticator
|
||
|
config :pleroma, :ldap,
|
||
|
enabled: true,
|
||
|
host: "localhost",
|
||
|
port: 389,
|
||
|
ssl: false,
|
||
|
base: "ou=pleroma,${domain}",
|
||
|
uid: "uid"
|
||
|
''
|
||
|
];
|
||
|
})
|
||
|
];
|
||
|
}
|