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}