{pkgs, config, ...}: with pkgs.lib; with pkgs; ###### interface let inherit mkOption mkIf optionalString stringAfter singleton; cfg = config.users.ldap; options = { users = { ldap = { enable = mkOption { default = false; description = " Whether to enable authentication against an LDAP server. "; }; server = mkOption { example = "ldap://ldap.example.org/"; description = " The URL of the LDAP server. "; }; base = mkOption { example = "dc=example,dc=org"; description = " The distinguished name of the search base. "; }; useTLS = mkOption { default = false; description = " If enabled, use TLS (encryption) over an LDAP (port 389) connection. The alternative is to specify an LDAPS server (port 636) in <option>users.ldap.server</option> or to forego security. "; }; timeLimit = mkOption { default = 0; type = types.int; description = " Specifies the time limit (in seconds) to use when performing searches. A value of zero (0), which is the default, is to wait indefinitely for searches to be completed. "; }; daemon = { enable = mkOption { default = false; description = '' Whether to let the nslcd daemon (nss-pam-ldapd) handle the LDAP lookups for NSS and PAM. This can improve performance, and if you need to bind to the LDAP server with a password, it increases security, since only the nslcd user needs to have access to the bindpw file, not everyone that uses NSS and/or PAM. If this option is enabled, a local nscd user is created automatically, and the nslcd service is started automatically when the network get up. ''; }; extraConfig = mkOption { default = ""; type = types.string; description = '' Extra configuration options that will be added verbatim at the end of the nslcd configuration file (nslcd.conf). '' ; } ; }; bind = { distinguishedName = mkOption { default = ""; example = "cn=admin,dc=example,dc=com"; type = types.string; description = " The distinguished name to bind to the LDAP server with. If this is not specified, an anonymous bind will be done. "; }; password = mkOption { default = "/etc/ldap/bind.password"; type = types.string; description = " The path to a file containing the credentials to use when binding to the LDAP server (if not binding anonymously). "; }; timeLimit = mkOption { default = 30; type = types.int; description = " Specifies the time limit (in seconds) to use when connecting to the directory server. This is distinct from the time limit specified in <literal>users.ldap.timeLimit</literal> and affects the initial server connection only. "; }; policy = mkOption { default = "hard_open"; type = types.string; description = " Specifies the policy to use for reconnecting to an unavailable LDAP server. The default is <literal>hard_open</literal>, which reconnects if opening the connection to the directory server failed. By contrast, <literal>hard_init</literal> reconnects if initializing the connection failed. Initializing may not actually contact the directory server, and it is possible that a malformed configuration file will trigger reconnection. If <literal>soft</literal> is specified, then <literal>nss_ldap</literal> will return immediately on server failure. All hard reconnect policies block with exponential backoff before retrying. "; }; }; extraConfig = mkOption { default = "" ; type = types.string ; description = '' Extra configuration options that will be added verbatim at the end of the ldap configuration file (ldap.conf). If <literal>users.ldap.daemon</literal> is enabled, this configuration will not be used. In that case, use <literal>users.ldap.daemon.extraConfig</literal> instead. '' ; }; }; }; }; # Careful: OpenLDAP seems to be very picky about the indentation of # this file. Directives HAVE to start in the first column! ldapConfig = { target = "ldap.conf"; source = writeText "ldap.conf" '' uri ${config.users.ldap.server} base ${config.users.ldap.base} timelimit ${toString config.users.ldap.timeLimit} bind_timelimit ${toString config.users.ldap.bind.timeLimit} bind_policy ${config.users.ldap.bind.policy} ${optionalString config.users.ldap.useTLS '' ssl start_tls tls_checkpeer no ''} ${optionalString (config.users.ldap.bind.distinguishedName != "") '' binddn ${config.users.ldap.bind.distinguishedName} ''} ${optionalString (cfg.extraConfig != "") cfg.extraConfig } ''; }; nslcdConfig = { target = "nslcd.conf"; source = writeText "nslcd.conf" '' uid nslcd gid nslcd uri ${cfg.server} base ${cfg.base} timelimit ${toString cfg.timeLimit} bind_timelimit ${toString cfg.bind.timeLimit} ${optionalString (cfg.bind.distinguishedName != "") "binddn ${cfg.bind.distinguishedName}" } ${optionalString (cfg.daemon.extraConfig != "") cfg.daemon.extraConfig } ''; }; insertLdapPassword = !config.users.ldap.daemon.enable && config.users.ldap.bind.distinguishedName != ""; in ###### implementation mkIf cfg.enable { require = [ options ]; environment.etc = if cfg.daemon.enable then [nslcdConfig] else [ldapConfig]; system.activationScripts = mkIf insertLdapPassword { ldap = stringAfter [ "etc" "groups" "users" ] '' if test -f "${cfg.bind.password}" ; then echo "bindpw "$(cat ${cfg.bind.password})"" | cat ${ldapConfig} - > /etc/ldap.conf.bindpw mv -fT /etc/ldap.conf.bindpw /etc/ldap.conf chmod 600 /etc/ldap.conf fi ''; }; system.nssModules = singleton ( if cfg.daemon.enable then nss_pam_ldapd else nss_ldap ); users = mkIf cfg.daemon.enable { extraGroups.nslcd = { gid = config.ids.gids.nslcd; }; extraUsers.nslcd = { uid = config.ids.uids.nslcd; description = "nslcd user."; group = "nslcd"; }; }; systemd.services = mkIf cfg.daemon.enable { nslcd = { wantedBy = [ "nss-user-lookup.target" ]; before = [ "nss-user-lookup.target" ]; after = [ "network.target" ]; preStart = '' mkdir -p /run/nslcd rm -f /run/nslcd/nslcd.pid; chown nslcd.nslcd /run/nslcd ${optionalString (cfg.bind.distinguishedName != "") '' if test -s "${cfg.bind.password}" ; then ln -sfT "${cfg.bind.password}" /run/nslcd/bindpw fi ''} ''; serviceConfig = { ExecStart = "${nss_pam_ldapd}/sbin/nslcd"; Type = "forking"; PIDFile = "/run/nslcd/nslcd.pid"; Restart = "always"; }; }; }; }