at 23.11-pre 9.8 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 (lib.mdDoc "authentication against an LDAP server"); 63 64 loginPam = mkOption { 65 type = types.bool; 66 default = true; 67 description = lib.mdDoc "Whether to include authentication against LDAP in login PAM."; 68 }; 69 70 nsswitch = mkOption { 71 type = types.bool; 72 default = true; 73 description = lib.mdDoc "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 = lib.mdDoc "The URL of the LDAP server."; 80 }; 81 82 base = mkOption { 83 type = types.str; 84 example = "dc=example,dc=org"; 85 description = lib.mdDoc "The distinguished name of the search base."; 86 }; 87 88 useTLS = mkOption { 89 type = types.bool; 90 default = false; 91 description = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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 = lib.mdDoc '' 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.activationScripts = mkIf (!cfg.daemon.enable) { 230 ldap = stringAfter [ "etc" "groups" "users" ] '' 231 if test -f "${cfg.bind.passwordFile}" ; then 232 umask 0077 233 conf="$(mktemp)" 234 printf 'bindpw %s\n' "$(cat ${cfg.bind.passwordFile})" | 235 cat ${ldapConfig.source} - >"$conf" 236 mv -fT "$conf" /etc/ldap.conf 237 fi 238 ''; 239 }; 240 241 system.nssModules = mkIf cfg.nsswitch (singleton ( 242 if cfg.daemon.enable then nss_pam_ldapd else nss_ldap 243 )); 244 245 system.nssDatabases.group = optional cfg.nsswitch "ldap"; 246 system.nssDatabases.passwd = optional cfg.nsswitch "ldap"; 247 system.nssDatabases.shadow = 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 = mkIf cfg.daemon.enable { 262 nslcd = { 263 wantedBy = [ "multi-user.target" ]; 264 265 preStart = '' 266 umask 0077 267 conf="$(mktemp)" 268 { 269 cat ${nslcdConfig} 270 test -z '${cfg.bind.distinguishedName}' -o ! -f '${cfg.bind.passwordFile}' || 271 printf 'bindpw %s\n' "$(cat '${cfg.bind.passwordFile}')" 272 test -z '${cfg.daemon.rootpwmoddn}' -o ! -f '${cfg.daemon.rootpwmodpwFile}' || 273 printf 'rootpwmodpw %s\n' "$(cat '${cfg.daemon.rootpwmodpwFile}')" 274 } >"$conf" 275 mv -fT "$conf" /run/nslcd/nslcd.conf 276 ''; 277 278 restartTriggers = [ 279 nslcdConfig 280 cfg.bind.passwordFile 281 cfg.daemon.rootpwmodpwFile 282 ]; 283 284 serviceConfig = { 285 ExecStart = "${nslcdWrapped}/bin/nslcd"; 286 Type = "forking"; 287 Restart = "always"; 288 User = "nslcd"; 289 Group = "nslcd"; 290 RuntimeDirectory = [ "nslcd" ]; 291 PIDFile = "/run/nslcd/nslcd.pid"; 292 AmbientCapabilities = "CAP_SYS_RESOURCE"; 293 }; 294 }; 295 296 }; 297 298 }; 299 300 imports = 301 [ (mkRenamedOptionModule [ "users" "ldap" "bind" "password"] [ "users" "ldap" "bind" "passwordFile"]) 302 ]; 303}