diff --git a/modules/config/ldap.nix b/modules/config/ldap.nix index 3821482361f..1e115943f23 100644 --- a/modules/config/ldap.nix +++ b/modules/config/ldap.nix @@ -1,8 +1,13 @@ {pkgs, config, ...}: +with pkgs.lib; +with pkgs; + ###### interface let - inherit (pkgs.lib) mkOption mkIf optionalString stringAfter; + inherit mkOption mkIf optionalString stringAfter singleton; + + cfg = config.users.ldap; options = { users = { @@ -41,7 +46,7 @@ let timeLimit = mkOption { default = 0; - type = with pkgs.lib.types; int; + 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 @@ -49,11 +54,36 @@ let "; }; + 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 = with pkgs.lib.types; string; + 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. @@ -62,7 +92,7 @@ let password = mkOption { default = "/etc/ldap/bind.password"; - type = with pkgs.lib.types; string; + type = types.string; description = " The path to a file containing the credentials to use when binding to the LDAP server (if not binding anonymously). @@ -71,7 +101,7 @@ let timeLimit = mkOption { default = 30; - type = with pkgs.lib.types; int; + 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 @@ -82,7 +112,7 @@ let policy = mkOption { default = "hard_open"; - type = with pkgs.lib.types; string; + type = types.string; description = " Specifies the policy to use for reconnecting to an unavailable LDAP server. The default is hard_open, which @@ -99,55 +129,120 @@ let }; }; + 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 users.ldap.daemon is enabled, this + configuration will not be used. In that case, use + users.ldap.daemon.extraConfig 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 config.users.ldap.enable { +mkIf cfg.enable { require = [ options ]; - # LDAP configuration. - environment = { - etc = [ + environment.etc = if cfg.daemon.enable then [nslcdConfig] else [ldapConfig]; - # Careful: OpenLDAP seems to be very picky about the indentation of - # this file. Directives HAVE to start in the first column! - { source = pkgs.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} - ''} - ''; - target = "ldap.conf"; - } - - ]; - }; - - system.activationScripts.ldap = stringAfter [ "etc" ] ( - optionalString (config.users.ldap.bind.distinguishedName != "") '' - if test -f "${config.users.ldap.bind.password}" ; then - echo "bindpw $(cat ${config.users.ldap.bind.password})" | cat /etc/ldap.conf - > /etc/ldap.conf.bindpw + 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"; + }; + }; + + jobs = mkIf cfg.daemon.enable { + nslcd = { + startOn = "filesystem"; + + stopOn = "stopping network-interfaces"; + + daemonType = "fork"; + + path = [ nss_pam_ldapd ]; + + preStart = '' + mkdir -p /run/nslcd + 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 + ''} + ''; + + postStop = "rm -f /run/nslcd/nslcd.pid"; + + exec = "nslcd"; + }; + }; } diff --git a/modules/config/nsswitch.nix b/modules/config/nsswitch.nix index 806ff876303..4660bf7c92d 100644 --- a/modules/config/nsswitch.nix +++ b/modules/config/nsswitch.nix @@ -19,14 +19,9 @@ let "; merge = mergeListOption; apply = list: - let - list2 = - list - # !!! this should be in the LDAP module - ++ optional config.users.ldap.enable pkgs.nss_ldap; - in { - list = list2; - path = makeLibraryPath list2; + { + inherit list; + path = makeLibraryPath list; }; }; diff --git a/modules/security/pam.nix b/modules/security/pam.nix index a77b7697379..d3dd4ef9ac0 100644 --- a/modules/security/pam.nix +++ b/modules/security/pam.nix @@ -7,7 +7,9 @@ with pkgs.lib; let - inherit (pkgs) pam_ldap pam_krb5 pam_ccreds; + inherit (pkgs) pam_krb5 pam_ccreds; + + pam_ldap = if config.users.ldap.daemon.enable then pkgs.nss_pam_ldapd else pkgs.pam_ldap; otherService = pkgs.writeText "other.pam" ''