1{ config, lib, pkgs, ... }:
2
3with pkgs;
4with lib;
5
6let
7
8 cfg = config.users.ldap;
9
10 # Careful: OpenLDAP seems to be very picky about the indentation of
11 # this file. Directives HAVE to start in the first column!
12 ldapConfig = {
13 target = "ldap.conf";
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 ''
21 ssl start_tls
22 tls_checkpeer no
23 ''}
24 ${optionalString (config.users.ldap.bind.distinguishedName != "") ''
25 binddn ${config.users.ldap.bind.distinguishedName}
26 ''}
27 ${optionalString (cfg.extraConfig != "") cfg.extraConfig }
28 '';
29 };
30
31 nslcdConfig = {
32 target = "nslcd.conf";
33 source = writeText "nslcd.conf" ''
34 uid nslcd
35 gid nslcd
36 uri ${cfg.server}
37 base ${cfg.base}
38 timelimit ${toString cfg.timeLimit}
39 bind_timelimit ${toString cfg.bind.timeLimit}
40 ${optionalString (cfg.bind.distinguishedName != "")
41 "binddn ${cfg.bind.distinguishedName}" }
42 ${optionalString (cfg.daemon.extraConfig != "") cfg.daemon.extraConfig }
43 '';
44 };
45
46 insertLdapPassword = !config.users.ldap.daemon.enable &&
47 config.users.ldap.bind.distinguishedName != "";
48
49in
50
51{
52
53 ###### interface
54
55 options = {
56
57 users.ldap = {
58
59 enable = mkOption {
60 type = types.bool;
61 default = false;
62 description = "Whether to enable authentication against an LDAP server.";
63 };
64
65 loginPam = mkOption {
66 type = types.bool;
67 default = true;
68 description = "Whether to include authentication against LDAP in login PAM";
69 };
70
71 nsswitch = mkOption {
72 type = types.bool;
73 default = true;
74 description = "Whether to include lookup against LDAP in NSS";
75 };
76
77 server = mkOption {
78 example = "ldap://ldap.example.org/";
79 description = "The URL of the LDAP server.";
80 };
81
82 base = mkOption {
83 example = "dc=example,dc=org";
84 description = "The distinguished name of the search base.";
85 };
86
87 useTLS = mkOption {
88 default = false;
89 description = ''
90 If enabled, use TLS (encryption) over an LDAP (port 389)
91 connection. The alternative is to specify an LDAPS server (port
92 636) in <option>users.ldap.server</option> or to forego
93 security.
94 '';
95 };
96
97 timeLimit = mkOption {
98 default = 0;
99 type = types.int;
100 description = ''
101 Specifies the time limit (in seconds) to use when performing
102 searches. A value of zero (0), which is the default, is to
103 wait indefinitely for searches to be completed.
104 '';
105 };
106
107 daemon = {
108 enable = mkOption {
109 default = false;
110 description = ''
111 Whether to let the nslcd daemon (nss-pam-ldapd) handle the
112 LDAP lookups for NSS and PAM. This can improve performance,
113 and if you need to bind to the LDAP server with a password,
114 it increases security, since only the nslcd user needs to
115 have access to the bindpw file, not everyone that uses NSS
116 and/or PAM. If this option is enabled, a local nscd user is
117 created automatically, and the nslcd service is started
118 automatically when the network get up.
119 '';
120 };
121
122 extraConfig = mkOption {
123 default = "";
124 type = types.lines;
125 description = ''
126 Extra configuration options that will be added verbatim at
127 the end of the nslcd configuration file (nslcd.conf).
128 '' ;
129 } ;
130 };
131
132 bind = {
133 distinguishedName = mkOption {
134 default = "";
135 example = "cn=admin,dc=example,dc=com";
136 type = types.str;
137 description = ''
138 The distinguished name to bind to the LDAP server with. If this
139 is not specified, an anonymous bind will be done.
140 '';
141 };
142
143 password = mkOption {
144 default = "/etc/ldap/bind.password";
145 type = types.str;
146 description = ''
147 The path to a file containing the credentials to use when binding
148 to the LDAP server (if not binding anonymously).
149 '';
150 };
151
152 timeLimit = mkOption {
153 default = 30;
154 type = types.int;
155 description = ''
156 Specifies the time limit (in seconds) to use when connecting
157 to the directory server. This is distinct from the time limit
158 specified in <literal>users.ldap.timeLimit</literal> and affects
159 the initial server connection only.
160 '';
161 };
162
163 policy = mkOption {
164 default = "hard_open";
165 type = types.enum [ "hard_open" "hard_init" "soft" ];
166 description = ''
167 Specifies the policy to use for reconnecting to an unavailable
168 LDAP server. The default is <literal>hard_open</literal>, which
169 reconnects if opening the connection to the directory server
170 failed. By contrast, <literal>hard_init</literal> reconnects if
171 initializing the connection failed. Initializing may not
172 actually contact the directory server, and it is possible that
173 a malformed configuration file will trigger reconnection. If
174 <literal>soft</literal> is specified, then
175 <literal>nss_ldap</literal> will return immediately on server
176 failure. All hard reconnect policies block with exponential
177 backoff before retrying.
178 '';
179 };
180 };
181
182 extraConfig = mkOption {
183 default = "";
184 type = types.lines;
185 description = ''
186 Extra configuration options that will be added verbatim at
187 the end of the ldap configuration file (ldap.conf).
188 If <literal>users.ldap.daemon</literal> is enabled, this
189 configuration will not be used. In that case, use
190 <literal>users.ldap.daemon.extraConfig</literal> instead.
191 '' ;
192 };
193
194 };
195
196 };
197
198 ###### implementation
199
200 config = mkIf cfg.enable {
201
202 environment.etc = if cfg.daemon.enable then [nslcdConfig] else [ldapConfig];
203
204 system.activationScripts = mkIf insertLdapPassword {
205 ldap = stringAfter [ "etc" "groups" "users" ] ''
206 if test -f "${cfg.bind.password}" ; then
207 echo "bindpw "$(cat ${cfg.bind.password})"" | cat ${ldapConfig.source} - > /etc/ldap.conf.bindpw
208 mv -fT /etc/ldap.conf.bindpw /etc/ldap.conf
209 chmod 600 /etc/ldap.conf
210 fi
211 '';
212 };
213
214 system.nssModules = singleton (
215 if cfg.daemon.enable then nss_pam_ldapd else nss_ldap
216 );
217
218 users = mkIf cfg.daemon.enable {
219 extraGroups.nslcd = {
220 gid = config.ids.gids.nslcd;
221 };
222
223 extraUsers.nslcd = {
224 uid = config.ids.uids.nslcd;
225 description = "nslcd user.";
226 group = "nslcd";
227 };
228 };
229
230 systemd.services = mkIf cfg.daemon.enable {
231
232 nslcd = {
233 wantedBy = [ "multi-user.target" ];
234
235 preStart = ''
236 mkdir -p /run/nslcd
237 rm -f /run/nslcd/nslcd.pid;
238 chown nslcd.nslcd /run/nslcd
239 ${optionalString (cfg.bind.distinguishedName != "") ''
240 if test -s "${cfg.bind.password}" ; then
241 ln -sfT "${cfg.bind.password}" /run/nslcd/bindpw
242 fi
243 ''}
244 '';
245
246 serviceConfig = {
247 ExecStart = "${nss_pam_ldapd}/sbin/nslcd";
248 Type = "forking";
249 PIDFile = "/run/nslcd/nslcd.pid";
250 Restart = "always";
251 };
252 };
253
254 };
255
256 };
257}