at 25.11-pre 10 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7 8let 9 inherit (lib) 10 mkEnableOption 11 mkIf 12 mkMerge 13 mkOption 14 mkRenamedOptionModule 15 types 16 ; 17 18 cfg = config.users.ldap; 19 20 # Careful: OpenLDAP seems to be very picky about the indentation of 21 # this file. Directives HAVE to start in the first column! 22 ldapConfig = { 23 target = "ldap.conf"; 24 source = pkgs.writeText "ldap.conf" '' 25 uri ${config.users.ldap.server} 26 base ${config.users.ldap.base} 27 timelimit ${toString config.users.ldap.timeLimit} 28 bind_timelimit ${toString config.users.ldap.bind.timeLimit} 29 bind_policy ${config.users.ldap.bind.policy} 30 ${lib.optionalString config.users.ldap.useTLS '' 31 ssl start_tls 32 ''} 33 ${lib.optionalString (config.users.ldap.bind.distinguishedName != "") '' 34 binddn ${config.users.ldap.bind.distinguishedName} 35 ''} 36 ${lib.optionalString (cfg.extraConfig != "") cfg.extraConfig} 37 ''; 38 }; 39 40 nslcdConfig = pkgs.writeText "nslcd.conf" '' 41 uri ${cfg.server} 42 base ${cfg.base} 43 timelimit ${toString cfg.timeLimit} 44 bind_timelimit ${toString cfg.bind.timeLimit} 45 ${lib.optionalString (cfg.bind.distinguishedName != "") "binddn ${cfg.bind.distinguishedName}"} 46 ${lib.optionalString (cfg.daemon.rootpwmoddn != "") "rootpwmoddn ${cfg.daemon.rootpwmoddn}"} 47 ${lib.optionalString (cfg.daemon.extraConfig != "") cfg.daemon.extraConfig} 48 ''; 49 50 # nslcd normally reads configuration from /etc/nslcd.conf. 51 # this file might contain secrets. We append those at runtime, 52 # so redirect its location to something more temporary. 53 nslcdWrapped = pkgs.runCommand "nslcd-wrapped" { nativeBuildInputs = [ pkgs.makeWrapper ]; } '' 54 mkdir -p $out/bin 55 makeWrapper ${pkgs.nss_pam_ldapd}/sbin/nslcd $out/bin/nslcd \ 56 --set LD_PRELOAD "${pkgs.libredirect}/lib/libredirect.so" \ 57 --set NIX_REDIRECTS "/etc/nslcd.conf=/run/nslcd/nslcd.conf" 58 ''; 59 60in 61 62{ 63 64 ###### interface 65 66 options = { 67 68 users.ldap = { 69 70 enable = mkEnableOption "authentication against an LDAP server"; 71 72 loginPam = mkOption { 73 type = types.bool; 74 default = true; 75 description = "Whether to include authentication against LDAP in login PAM."; 76 }; 77 78 nsswitch = mkOption { 79 type = types.bool; 80 default = true; 81 description = "Whether to include lookup against LDAP in NSS."; 82 }; 83 84 server = mkOption { 85 type = types.str; 86 example = "ldap://ldap.example.org/"; 87 description = "The URL of the LDAP server."; 88 }; 89 90 base = mkOption { 91 type = types.str; 92 example = "dc=example,dc=org"; 93 description = "The distinguished name of the search base."; 94 }; 95 96 useTLS = mkOption { 97 type = types.bool; 98 default = false; 99 description = '' 100 If enabled, use TLS (encryption) over an LDAP (port 389) 101 connection. The alternative is to specify an LDAPS server (port 102 636) in {option}`users.ldap.server` or to forego 103 security. 104 ''; 105 }; 106 107 timeLimit = mkOption { 108 default = 0; 109 type = types.int; 110 description = '' 111 Specifies the time limit (in seconds) to use when performing 112 searches. A value of zero (0), which is the default, is to 113 wait indefinitely for searches to be completed. 114 ''; 115 }; 116 117 daemon = { 118 enable = mkOption { 119 type = types.bool; 120 default = false; 121 description = '' 122 Whether to let the nslcd daemon (nss-pam-ldapd) handle the 123 LDAP lookups for NSS and PAM. This can improve performance, 124 and if you need to bind to the LDAP server with a password, 125 it increases security, since only the nslcd user needs to 126 have access to the bindpw file, not everyone that uses NSS 127 and/or PAM. If this option is enabled, a local nscd user is 128 created automatically, and the nslcd service is started 129 automatically when the network get up. 130 ''; 131 }; 132 133 extraConfig = mkOption { 134 default = ""; 135 type = types.lines; 136 description = '' 137 Extra configuration options that will be added verbatim at 138 the end of the nslcd configuration file ({manpage}`nslcd.conf(5)`). 139 ''; 140 }; 141 142 rootpwmoddn = mkOption { 143 default = ""; 144 example = "cn=admin,dc=example,dc=com"; 145 type = types.str; 146 description = '' 147 The distinguished name to use to bind to the LDAP server 148 when the root user tries to modify a user's password. 149 ''; 150 }; 151 152 rootpwmodpwFile = mkOption { 153 default = ""; 154 example = "/run/keys/nslcd.rootpwmodpw"; 155 type = types.str; 156 description = '' 157 The path to a file containing the credentials with which to bind to 158 the LDAP server if the root user tries to change a user's password. 159 ''; 160 }; 161 }; 162 163 bind = { 164 distinguishedName = mkOption { 165 default = ""; 166 example = "cn=admin,dc=example,dc=com"; 167 type = types.str; 168 description = '' 169 The distinguished name to bind to the LDAP server with. If this 170 is not specified, an anonymous bind will be done. 171 ''; 172 }; 173 174 passwordFile = mkOption { 175 default = "/etc/ldap/bind.password"; 176 type = types.str; 177 description = '' 178 The path to a file containing the credentials to use when binding 179 to the LDAP server (if not binding anonymously). 180 ''; 181 }; 182 183 timeLimit = mkOption { 184 default = 30; 185 type = types.int; 186 description = '' 187 Specifies the time limit (in seconds) to use when connecting 188 to the directory server. This is distinct from the time limit 189 specified in {option}`users.ldap.timeLimit` and affects 190 the initial server connection only. 191 ''; 192 }; 193 194 policy = mkOption { 195 default = "hard_open"; 196 type = types.enum [ 197 "hard_open" 198 "hard_init" 199 "soft" 200 ]; 201 description = '' 202 Specifies the policy to use for reconnecting to an unavailable 203 LDAP server. The default is `hard_open`, which 204 reconnects if opening the connection to the directory server 205 failed. By contrast, `hard_init` reconnects if 206 initializing the connection failed. Initializing may not 207 actually contact the directory server, and it is possible that 208 a malformed configuration file will trigger reconnection. If 209 `soft` is specified, then 210 `nss_ldap` will return immediately on server 211 failure. All hard reconnect policies block with exponential 212 backoff before retrying. 213 ''; 214 }; 215 }; 216 217 extraConfig = mkOption { 218 default = ""; 219 type = types.lines; 220 description = '' 221 Extra configuration options that will be added verbatim at 222 the end of the ldap configuration file ({manpage}`ldap.conf(5)`). 223 If {option}`users.ldap.daemon` is enabled, this 224 configuration will not be used. In that case, use 225 {option}`users.ldap.daemon.extraConfig` instead. 226 ''; 227 }; 228 229 }; 230 231 }; 232 233 ###### implementation 234 235 config = mkIf cfg.enable { 236 237 environment.etc = lib.optionalAttrs (!cfg.daemon.enable) { 238 "ldap.conf" = ldapConfig; 239 }; 240 241 system.nssModules = mkIf cfg.nsswitch ( 242 lib.singleton (if cfg.daemon.enable then pkgs.nss_pam_ldapd else pkgs.nss_ldap) 243 ); 244 245 system.nssDatabases.group = lib.optional cfg.nsswitch "ldap"; 246 system.nssDatabases.passwd = lib.optional cfg.nsswitch "ldap"; 247 system.nssDatabases.shadow = lib.optional cfg.nsswitch "ldap"; 248 249 users = mkIf cfg.daemon.enable { 250 groups.nslcd = { 251 gid = config.ids.gids.nslcd; 252 }; 253 254 users.nslcd = { 255 uid = config.ids.uids.nslcd; 256 description = "nslcd user."; 257 group = "nslcd"; 258 }; 259 }; 260 261 systemd.services = mkMerge [ 262 (mkIf (!cfg.daemon.enable) { 263 ldap-password = { 264 wantedBy = [ "sysinit.target" ]; 265 before = [ 266 "sysinit.target" 267 "shutdown.target" 268 ]; 269 conflicts = [ "shutdown.target" ]; 270 unitConfig.DefaultDependencies = false; 271 serviceConfig.Type = "oneshot"; 272 serviceConfig.RemainAfterExit = true; 273 script = '' 274 if test -f "${cfg.bind.passwordFile}" ; then 275 umask 0077 276 conf="$(mktemp)" 277 printf 'bindpw %s\n' "$(cat ${cfg.bind.passwordFile})" | 278 cat ${ldapConfig.source} - >"$conf" 279 mv -fT "$conf" /etc/ldap.conf 280 fi 281 ''; 282 }; 283 }) 284 285 (mkIf cfg.daemon.enable { 286 nslcd = { 287 wantedBy = [ "multi-user.target" ]; 288 289 preStart = '' 290 umask 0077 291 conf="$(mktemp)" 292 { 293 cat ${nslcdConfig} 294 test -z '${cfg.bind.distinguishedName}' -o ! -f '${cfg.bind.passwordFile}' || 295 printf 'bindpw %s\n' "$(cat '${cfg.bind.passwordFile}')" 296 test -z '${cfg.daemon.rootpwmoddn}' -o ! -f '${cfg.daemon.rootpwmodpwFile}' || 297 printf 'rootpwmodpw %s\n' "$(cat '${cfg.daemon.rootpwmodpwFile}')" 298 } >"$conf" 299 mv -fT "$conf" /run/nslcd/nslcd.conf 300 ''; 301 302 restartTriggers = [ 303 nslcdConfig 304 cfg.bind.passwordFile 305 cfg.daemon.rootpwmodpwFile 306 ]; 307 308 serviceConfig = { 309 ExecStart = "${nslcdWrapped}/bin/nslcd"; 310 Type = "forking"; 311 Restart = "always"; 312 User = "nslcd"; 313 Group = "nslcd"; 314 RuntimeDirectory = [ "nslcd" ]; 315 PIDFile = "/run/nslcd/nslcd.pid"; 316 AmbientCapabilities = "CAP_SYS_RESOURCE"; 317 }; 318 }; 319 }) 320 ]; 321 322 }; 323 324 imports = [ 325 (mkRenamedOptionModule 326 [ "users" "ldap" "bind" "password" ] 327 [ "users" "ldap" "bind" "passwordFile" ] 328 ) 329 ]; 330}