1
0
Fork 0
selfhostblocks/modules/blocks/ldap.nix
2024-06-10 08:38:01 -07:00

303 lines
9.1 KiB
Nix

{ config, pkgs, lib, ... }:
let
cfg = config.shb.ldap;
contracts = pkgs.callPackage ../contracts {};
fqdn = "${cfg.subdomain}.${cfg.domain}";
lldap-cli-auth = pkgs.callPackage ({ stdenvNoCC, makeWrapper, lldap-cli }: stdenvNoCC.mkDerivation {
name = "lldap-cli";
src = lldap-cli;
nativeBuildInputs = [
makeWrapper
];
# No quotes around the value for LLDAP_PASSWORD because we want the value to not be enclosed in quotes.
installPhase = ''
makeWrapper ${pkgs.lldap-cli}/bin/lldap-cli $out/bin/lldap-cli \
--set LLDAP_USERNAME "admin" \
--set LLDAP_PASSWORD $(cat ${cfg.ldapUserPasswordFile}) \
--set LLDAP_HTTPURL "http://${config.services.lldap.settings.http_host}:${toString config.services.lldap.settings.http_port}"
'';
}) {};
in
{
options.shb.ldap = {
enable = lib.mkEnableOption "the LDAP service";
dcdomain = lib.mkOption {
type = lib.types.str;
description = "dc domain to serve.";
example = "dc=mydomain,dc=com";
};
subdomain = lib.mkOption {
type = lib.types.str;
description = "Subdomain under which the LDAP service will be served.";
example = "grafana";
};
domain = lib.mkOption {
type = lib.types.str;
description = "Domain under which the LDAP service will be served.";
example = "mydomain.com";
};
ldapPort = lib.mkOption {
type = lib.types.port;
description = "Port on which the server listens for the LDAP protocol.";
default = 3890;
};
ssl = lib.mkOption {
description = "Path to SSL files";
type = lib.types.nullOr contracts.ssl.certs;
default = null;
};
webUIListenPort = lib.mkOption {
type = lib.types.port;
description = "Port on which the web UI is exposed.";
default = 17170;
};
ldapUserPasswordFile = lib.mkOption {
type = lib.types.path;
description = "File containing the LDAP admin user password.";
};
jwtSecretFile = lib.mkOption {
type = lib.types.path;
description = "File containing the JWT secret.";
};
restrictAccessIPRange = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Set a local network range to restrict access to the UI to only those IPs.";
example = "192.168.1.1/24";
default = null;
};
debug = lib.mkOption {
description = "Enable debug logging.";
type = lib.types.bool;
default = false;
};
groups = lib.mkOption {
description = "LDAP Groups to manage declaratively.";
default = {};
example = lib.literalExpression ''
{
family = {};
}
'';
type = lib.types.attrsOf (lib.types.submodule {
options = {};
});
};
users = lib.mkOption {
description = "LDAP Users to manage declaratively.";
default = {};
type = lib.types.attrsOf (lib.types.submodule {
options = {
email = lib.mkOption {
description = "Email address.";
type = lib.types.str;
};
displayName = lib.mkOption {
description = "Display name.";
type = lib.types.str;
};
firstName = lib.mkOption {
description = "First name.";
type = lib.types.str;
};
lastName = lib.mkOption {
description = "Last name.";
type = lib.types.str;
};
groups = lib.mkOption {
description = "Groups this user is member of. The group must exist.";
type = lib.types.listOf lib.types.str;
default = [];
};
passwordFile = lib.mkOption {
type = lib.types.path;
description = "File containing the user's password.";
};
};
});
};
};
config = lib.mkIf cfg.enable {
services.nginx = {
enable = true;
virtualHosts.${fqdn} = {
forceSSL = !(isNull cfg.ssl);
sslCertificate = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.cert;
sslCertificateKey = lib.mkIf (!(isNull cfg.ssl)) cfg.ssl.paths.key;
locations."/" = {
extraConfig = ''
proxy_set_header Host $host;
'' + (if isNull cfg.restrictAccessIPRange then "" else ''
allow ${cfg.restrictAccessIPRange};
deny all;
'');
proxyPass = "http://${toString config.services.lldap.settings.http_host}:${toString config.services.lldap.settings.http_port}/";
};
};
};
users.users.lldap = {
name = "lldap";
group = "lldap";
isSystemUser = true;
};
users.groups.lldap = {
members = [ "backup" ];
};
services.lldap = {
enable = true;
environment = {
LLDAP_JWT_SECRET_FILE = toString cfg.jwtSecretFile;
LLDAP_LDAP_USER_PASS_FILE = toString cfg.ldapUserPasswordFile;
RUST_LOG = lib.mkIf cfg.debug "debug";
};
settings = {
http_url = "https://${fqdn}";
http_host = "127.0.0.1";
http_port = cfg.webUIListenPort;
ldap_host = "127.0.0.1";
ldap_port = cfg.ldapPort;
ldap_base_dn = cfg.dcdomain;
verbose = cfg.debug;
};
};
environment.systemPackages = [
lldap-cli-auth
];
# $ lldap-cli schema attribute user list
#
# Name Type Is list Is visible Is editable
# ---- ---- ------- ---------- -----------
# avatar JpegPhoto false true true
# creation_date DateTime false true false
# display_name String false true true
# first_name String false true true
# last_name String false true true
# mail String false true true
# user_id String false true false
# uuid String false true false
# $ lldap-cli schema attribute group list
#
# Name Type Is list Is visible Is editable
# ---- ---- ------- ---------- -----------
# creation_date DateTime false true false
# display_name String false true true
# group_id Integer false true false
# uuid String false true false
systemd.services.lldap.postStart =
let
configFile = (pkgs.formats.toml {}).generate "lldap_config.toml" config.services.lldap.settings;
login = [''
set -euo pipefail
sleep 3
export LLDAP_USERNAME=admin
export LLDAP_PASSWORD=$(cat ${cfg.ldapUserPasswordFile})
export LLDAP_HTTPURL=http://${config.services.lldap.settings.http_host}:${toString config.services.lldap.settings.http_port}
eval $(${pkgs.lldap-cli}/bin/lldap-cli login)
set -x
''];
deleteGroups = [''
allUids=(${lib.concatStringsSep " " (
(lib.mapAttrsToList (uid: g: uid) cfg.groups)
++ [ "lldap_admin" "lldap_password_manager" "lldap_strict_readonly" ])
})
echo All managed groups are: $allUids
echo Other groups will be deleted
for uid in $(${pkgs.lldap-cli}/bin/lldap-cli group list); do
if [[ ! " ''${allUids[*]} " =~ [[:space:]]''${uid}[[:space:]] ]]; then
${pkgs.lldap-cli}/bin/lldap-cli group del $uid
fi
done
''];
createGroups = lib.mapAttrsToList (uid: g: ''
${pkgs.lldap-cli}/bin/lldap-cli group add ${uid}
'') cfg.groups;
deleteUsers = [''
allUids=(${lib.concatStringsSep " " (
(lib.mapAttrsToList (uid: u: uid) cfg.users)
++ [ "admin" ])
})
for uid in $(${pkgs.lldap-cli}/bin/lldap-cli user list uid); do
if [[ ! " ''${allUids[*]} " =~ [[:space:]]''${uid}[[:space:]] ]]; then
${pkgs.lldap-cli}/bin/lldap-cli user del $uid
fi
done
''];
createUsers = lib.mapAttrsToList (uid: u: ''
${pkgs.lldap-cli}/bin/lldap-cli user add ${uid} "${u.email}"
${pkgs.lldap-cli}/bin/lldap-cli user update set ${uid} password "$(cat ${u.passwordFile})"
${pkgs.lldap-cli}/bin/lldap-cli user update set ${uid} mail "${u.email}"
${pkgs.lldap-cli}/bin/lldap-cli user update set ${uid} display_name "${u.displayName}"
# ${pkgs.lldap-cli}/bin/lldap-cli user update set ${uid} first_name "${u.firstName}"
# ${pkgs.lldap-cli}/bin/lldap-cli user update set ${uid} last_name "${u.lastName}"
'') cfg.users;
addToGroups = lib.mapAttrsToList (uid: u: lib.concatMapStringsSep "\n" (g: ''
${pkgs.lldap-cli}/bin/lldap-cli user group add \
${uid} \
${g}
'') u.groups) cfg.users;
in
lib.concatStringsSep "\n\n" (
login
++ deleteGroups
++ createGroups
++ deleteUsers
++ createUsers
++ addToGroups
);
shb.backup.instances.lldap = {
sourceDirectories = [
"/var/lib/lldap"
];
};
};
}