mirror of
https://git.selfprivacy.org/SelfPrivacy/selfprivacy-nixos-config.git
synced 2025-01-04 23:24:22 +00:00
feat(ldap): Add LDAP support
Co-authored-by: misuzu <bakalolka@gmail.com>
This commit is contained in:
parent
7e590ae60c
commit
82578e3531
|
@ -10,6 +10,7 @@ in
|
|||
./variables.nix
|
||||
./files.nix
|
||||
./volumes.nix
|
||||
./ldap/ldap-module.nix
|
||||
./users.nix
|
||||
./mailserver/system/mailserver.nix
|
||||
./vpn/ocserv.nix
|
||||
|
|
|
@ -13,10 +13,10 @@ in
|
|||
gitea = {
|
||||
enable = cfg.gitea.enable;
|
||||
stateDir = "/var/lib/gitea";
|
||||
# log = {
|
||||
# rootPath = "/var/lib/gitea/log";
|
||||
# level = "Warn";
|
||||
# };
|
||||
# log = {
|
||||
# rootPath = "/var/lib/gitea/log";
|
||||
# level = "Warn";
|
||||
# };
|
||||
user = "gitea";
|
||||
database = {
|
||||
type = "sqlite3";
|
||||
|
@ -40,7 +40,7 @@ in
|
|||
rootUrl = "https://git.${cfg.domain}/";
|
||||
httpAddress = "0.0.0.0";
|
||||
httpPort = 3000;
|
||||
# cookieSecure = true;
|
||||
# cookieSecure = true;
|
||||
settings = {
|
||||
mailer = {
|
||||
ENABLED = false;
|
||||
|
|
339
ldap/ldap-module.nix
Normal file
339
ldap/ldap-module.nix
Normal file
|
@ -0,0 +1,339 @@
|
|||
{ 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"
|
||||
''
|
||||
];
|
||||
})
|
||||
];
|
||||
}
|
16
users.nix
16
users.nix
|
@ -22,4 +22,20 @@ in
|
|||
})
|
||||
cfg.users);
|
||||
};
|
||||
selfprivacy.ldap = {
|
||||
enable = true;
|
||||
domain = "${cfg.domain}";
|
||||
rootUser = "${cfg.username}";
|
||||
rootHashedPassword = cfg.hashedMasterPassword;
|
||||
users = [
|
||||
(builtins.map
|
||||
(user: {
|
||||
username = "${user.username}";
|
||||
email = "${user.username}@${cfg.domain}";
|
||||
hashedPassword = user.hashedPassword;
|
||||
groups = [ "gitea" "nextcloud" "pleroma" ];
|
||||
})
|
||||
cfg.users)
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue