1 { config, lib, pkgs, ... }:
8 cfg = config.users.ldap;
10 # Careful: OpenLDAP seems to be very picky about the indentation of
11 # this file. Directives HAVE to start in the first column!
14 source = writeText "ldap.conf" ''
15 uri ${config.users.ldap.server}
16 base ${config.users.ldap.base}
17 timelimit ${toString config.users.ldap.timeLimit}
18 bind_timelimit ${toString config.users.ldap.bind.timeLimit}
19 bind_policy ${config.users.ldap.bind.policy}
20 ${optionalString config.users.ldap.useTLS ''
23 ${optionalString (config.users.ldap.bind.distinguishedName != "") ''
24 binddn ${config.users.ldap.bind.distinguishedName}
26 ${optionalString (cfg.extraConfig != "") cfg.extraConfig }
30 nslcdConfig = writeText "nslcd.conf" ''
33 timelimit ${toString cfg.timeLimit}
34 bind_timelimit ${toString cfg.bind.timeLimit}
35 ${optionalString (cfg.bind.distinguishedName != "")
36 "binddn ${cfg.bind.distinguishedName}" }
37 ${optionalString (cfg.daemon.rootpwmoddn != "")
38 "rootpwmoddn ${cfg.daemon.rootpwmoddn}" }
39 ${optionalString (cfg.daemon.extraConfig != "") cfg.daemon.extraConfig }
42 # nslcd normally reads configuration from /etc/nslcd.conf.
43 # this file might contain secrets. We append those at runtime,
44 # so redirect its location to something more temporary.
45 nslcdWrapped = runCommand "nslcd-wrapped" { nativeBuildInputs = [ makeWrapper ]; } ''
47 makeWrapper ${nss_pam_ldapd}/sbin/nslcd $out/bin/nslcd \
48 --set LD_PRELOAD "${pkgs.libredirect}/lib/libredirect.so" \
49 --set NIX_REDIRECTS "/etc/nslcd.conf=/run/nslcd/nslcd.conf"
62 enable = mkEnableOption (lib.mdDoc "authentication against an LDAP server");
67 description = lib.mdDoc "Whether to include authentication against LDAP in login PAM.";
73 description = lib.mdDoc "Whether to include lookup against LDAP in NSS.";
78 example = "ldap://ldap.example.org/";
79 description = lib.mdDoc "The URL of the LDAP server.";
84 example = "dc=example,dc=org";
85 description = lib.mdDoc "The distinguished name of the search base.";
91 description = lib.mdDoc ''
92 If enabled, use TLS (encryption) over an LDAP (port 389)
93 connection. The alternative is to specify an LDAPS server (port
94 636) in {option}`users.ldap.server` or to forego
99 timeLimit = mkOption {
102 description = lib.mdDoc ''
103 Specifies the time limit (in seconds) to use when performing
104 searches. A value of zero (0), which is the default, is to
105 wait indefinitely for searches to be completed.
113 description = lib.mdDoc ''
114 Whether to let the nslcd daemon (nss-pam-ldapd) handle the
115 LDAP lookups for NSS and PAM. This can improve performance,
116 and if you need to bind to the LDAP server with a password,
117 it increases security, since only the nslcd user needs to
118 have access to the bindpw file, not everyone that uses NSS
119 and/or PAM. If this option is enabled, a local nscd user is
120 created automatically, and the nslcd service is started
121 automatically when the network get up.
125 extraConfig = mkOption {
128 description = lib.mdDoc ''
129 Extra configuration options that will be added verbatim at
130 the end of the nslcd configuration file (`nslcd.conf(5)`).
134 rootpwmoddn = mkOption {
136 example = "cn=admin,dc=example,dc=com";
138 description = lib.mdDoc ''
139 The distinguished name to use to bind to the LDAP server
140 when the root user tries to modify a user's password.
144 rootpwmodpwFile = mkOption {
146 example = "/run/keys/nslcd.rootpwmodpw";
148 description = lib.mdDoc ''
149 The path to a file containing the credentials with which to bind to
150 the LDAP server if the root user tries to change a user's password.
156 distinguishedName = mkOption {
158 example = "cn=admin,dc=example,dc=com";
160 description = lib.mdDoc ''
161 The distinguished name to bind to the LDAP server with. If this
162 is not specified, an anonymous bind will be done.
166 passwordFile = mkOption {
167 default = "/etc/ldap/bind.password";
169 description = lib.mdDoc ''
170 The path to a file containing the credentials to use when binding
171 to the LDAP server (if not binding anonymously).
175 timeLimit = mkOption {
178 description = lib.mdDoc ''
179 Specifies the time limit (in seconds) to use when connecting
180 to the directory server. This is distinct from the time limit
181 specified in {option}`users.ldap.timeLimit` and affects
182 the initial server connection only.
187 default = "hard_open";
188 type = types.enum [ "hard_open" "hard_init" "soft" ];
189 description = lib.mdDoc ''
190 Specifies the policy to use for reconnecting to an unavailable
191 LDAP server. The default is `hard_open`, which
192 reconnects if opening the connection to the directory server
193 failed. By contrast, `hard_init` reconnects if
194 initializing the connection failed. Initializing may not
195 actually contact the directory server, and it is possible that
196 a malformed configuration file will trigger reconnection. If
197 `soft` is specified, then
198 `nss_ldap` will return immediately on server
199 failure. All hard reconnect policies block with exponential
200 backoff before retrying.
205 extraConfig = mkOption {
208 description = lib.mdDoc ''
209 Extra configuration options that will be added verbatim at
210 the end of the ldap configuration file (`ldap.conf(5)`).
211 If {option}`users.ldap.daemon` is enabled, this
212 configuration will not be used. In that case, use
213 {option}`users.ldap.daemon.extraConfig` instead.
221 ###### implementation
223 config = mkIf cfg.enable {
225 environment.etc = optionalAttrs (!cfg.daemon.enable) {
226 "ldap.conf" = ldapConfig;
229 system.activationScripts = mkIf (!cfg.daemon.enable) {
230 ldap = stringAfter [ "etc" "groups" "users" ] ''
231 if test -f "${cfg.bind.passwordFile}" ; then
234 printf 'bindpw %s\n' "$(cat ${cfg.bind.passwordFile})" |
235 cat ${ldapConfig.source} - >"$conf"
236 mv -fT "$conf" /etc/ldap.conf
241 system.nssModules = mkIf cfg.nsswitch (singleton (
242 if cfg.daemon.enable then nss_pam_ldapd else nss_ldap
245 system.nssDatabases.group = optional cfg.nsswitch "ldap";
246 system.nssDatabases.passwd = optional cfg.nsswitch "ldap";
247 system.nssDatabases.shadow = optional cfg.nsswitch "ldap";
249 users = mkIf cfg.daemon.enable {
251 gid = config.ids.gids.nslcd;
255 uid = config.ids.uids.nslcd;
256 description = "nslcd user.";
261 systemd.services = mkIf cfg.daemon.enable {
263 wantedBy = [ "multi-user.target" ];
270 test -z '${cfg.bind.distinguishedName}' -o ! -f '${cfg.bind.passwordFile}' ||
271 printf 'bindpw %s\n' "$(cat '${cfg.bind.passwordFile}')"
272 test -z '${cfg.daemon.rootpwmoddn}' -o ! -f '${cfg.daemon.rootpwmodpwFile}' ||
273 printf 'rootpwmodpw %s\n' "$(cat '${cfg.daemon.rootpwmodpwFile}')"
275 mv -fT "$conf" /run/nslcd/nslcd.conf
280 cfg.bind.passwordFile
281 cfg.daemon.rootpwmodpwFile
285 ExecStart = "${nslcdWrapped}/bin/nslcd";
290 RuntimeDirectory = [ "nslcd" ];
291 PIDFile = "/run/nslcd/nslcd.pid";
292 AmbientCapabilities = "CAP_SYS_RESOURCE";
301 [ (mkRenamedOptionModule [ "users" "ldap" "bind" "password"] [ "users" "ldap" "bind" "passwordFile"])