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