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