at 24.11-pre 10 kB view raw
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 = writeText "nslcd.conf" '' 31 uri ${cfg.server} 32 base ${cfg.base} 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 } 40 ''; 41 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 ]; } '' 46 mkdir -p $out/bin 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" 50 ''; 51 52in 53 54{ 55 56 ###### interface 57 58 options = { 59 60 users.ldap = { 61 62 enable = mkEnableOption "authentication against an LDAP server"; 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 type = types.str; 78 example = "ldap://ldap.example.org/"; 79 description = "The URL of the LDAP server."; 80 }; 81 82 base = mkOption { 83 type = types.str; 84 example = "dc=example,dc=org"; 85 description = "The distinguished name of the search base."; 86 }; 87 88 useTLS = mkOption { 89 type = types.bool; 90 default = false; 91 description = '' 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 95 security. 96 ''; 97 }; 98 99 timeLimit = mkOption { 100 default = 0; 101 type = types.int; 102 description = '' 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. 106 ''; 107 }; 108 109 daemon = { 110 enable = mkOption { 111 type = types.bool; 112 default = false; 113 description = '' 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. 122 ''; 123 }; 124 125 extraConfig = mkOption { 126 default = ""; 127 type = types.lines; 128 description = '' 129 Extra configuration options that will be added verbatim at 130 the end of the nslcd configuration file (`nslcd.conf(5)`). 131 ''; 132 } ; 133 134 rootpwmoddn = mkOption { 135 default = ""; 136 example = "cn=admin,dc=example,dc=com"; 137 type = types.str; 138 description = '' 139 The distinguished name to use to bind to the LDAP server 140 when the root user tries to modify a user's password. 141 ''; 142 }; 143 144 rootpwmodpwFile = mkOption { 145 default = ""; 146 example = "/run/keys/nslcd.rootpwmodpw"; 147 type = types.str; 148 description = '' 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. 151 ''; 152 }; 153 }; 154 155 bind = { 156 distinguishedName = mkOption { 157 default = ""; 158 example = "cn=admin,dc=example,dc=com"; 159 type = types.str; 160 description = '' 161 The distinguished name to bind to the LDAP server with. If this 162 is not specified, an anonymous bind will be done. 163 ''; 164 }; 165 166 passwordFile = mkOption { 167 default = "/etc/ldap/bind.password"; 168 type = types.str; 169 description = '' 170 The path to a file containing the credentials to use when binding 171 to the LDAP server (if not binding anonymously). 172 ''; 173 }; 174 175 timeLimit = mkOption { 176 default = 30; 177 type = types.int; 178 description = '' 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. 183 ''; 184 }; 185 186 policy = mkOption { 187 default = "hard_open"; 188 type = types.enum [ "hard_open" "hard_init" "soft" ]; 189 description = '' 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. 201 ''; 202 }; 203 }; 204 205 extraConfig = mkOption { 206 default = ""; 207 type = types.lines; 208 description = '' 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. 214 ''; 215 }; 216 217 }; 218 219 }; 220 221 ###### implementation 222 223 config = mkIf cfg.enable { 224 225 environment.etc = optionalAttrs (!cfg.daemon.enable) { 226 "ldap.conf" = ldapConfig; 227 }; 228 229 system.nssModules = mkIf cfg.nsswitch (singleton ( 230 if cfg.daemon.enable then nss_pam_ldapd else nss_ldap 231 )); 232 233 system.nssDatabases.group = optional cfg.nsswitch "ldap"; 234 system.nssDatabases.passwd = optional cfg.nsswitch "ldap"; 235 system.nssDatabases.shadow = optional cfg.nsswitch "ldap"; 236 237 users = mkIf cfg.daemon.enable { 238 groups.nslcd = { 239 gid = config.ids.gids.nslcd; 240 }; 241 242 users.nslcd = { 243 uid = config.ids.uids.nslcd; 244 description = "nslcd user."; 245 group = "nslcd"; 246 }; 247 }; 248 249 systemd.services = mkMerge [ 250 (mkIf (!cfg.daemon.enable) { 251 ldap-password = { 252 wantedBy = [ "sysinit.target" ]; 253 before = [ "sysinit.target" "shutdown.target" ]; 254 conflicts = [ "shutdown.target" ]; 255 unitConfig.DefaultDependencies = false; 256 serviceConfig.Type = "oneshot"; 257 serviceConfig.RemainAfterExit = true; 258 script = '' 259 if test -f "${cfg.bind.passwordFile}" ; then 260 umask 0077 261 conf="$(mktemp)" 262 printf 'bindpw %s\n' "$(cat ${cfg.bind.passwordFile})" | 263 cat ${ldapConfig.source} - >"$conf" 264 mv -fT "$conf" /etc/ldap.conf 265 fi 266 ''; 267 }; 268 }) 269 270 (mkIf cfg.daemon.enable { 271 nslcd = { 272 wantedBy = [ "multi-user.target" ]; 273 274 preStart = '' 275 umask 0077 276 conf="$(mktemp)" 277 { 278 cat ${nslcdConfig} 279 test -z '${cfg.bind.distinguishedName}' -o ! -f '${cfg.bind.passwordFile}' || 280 printf 'bindpw %s\n' "$(cat '${cfg.bind.passwordFile}')" 281 test -z '${cfg.daemon.rootpwmoddn}' -o ! -f '${cfg.daemon.rootpwmodpwFile}' || 282 printf 'rootpwmodpw %s\n' "$(cat '${cfg.daemon.rootpwmodpwFile}')" 283 } >"$conf" 284 mv -fT "$conf" /run/nslcd/nslcd.conf 285 ''; 286 287 restartTriggers = [ 288 nslcdConfig 289 cfg.bind.passwordFile 290 cfg.daemon.rootpwmodpwFile 291 ]; 292 293 serviceConfig = { 294 ExecStart = "${nslcdWrapped}/bin/nslcd"; 295 Type = "forking"; 296 Restart = "always"; 297 User = "nslcd"; 298 Group = "nslcd"; 299 RuntimeDirectory = [ "nslcd" ]; 300 PIDFile = "/run/nslcd/nslcd.pid"; 301 AmbientCapabilities = "CAP_SYS_RESOURCE"; 302 }; 303 }; 304 }) 305 ]; 306 307 }; 308 309 imports = 310 [ (mkRenamedOptionModule [ "users" "ldap" "bind" "password"] [ "users" "ldap" "bind" "passwordFile"]) 311 ]; 312}