at 21.11-pre 13 kB view raw
1{ config, lib, pkgs, ... }: 2 3with lib; 4let 5 cfg = config.services.openldap; 6 legacyOptions = [ "rootpwFile" "suffix" "dataDir" "rootdn" "rootpw" ]; 7 openldap = cfg.package; 8 configDir = if cfg.configDir != null then cfg.configDir else "/etc/openldap/slapd.d"; 9 10 ldapValueType = let 11 # Can't do types.either with multiple non-overlapping submodules, so define our own 12 singleLdapValueType = lib.mkOptionType rec { 13 name = "LDAP"; 14 description = "LDAP value"; 15 check = x: lib.isString x || (lib.isAttrs x && (x ? path || x ? base64)); 16 merge = lib.mergeEqualOption; 17 }; 18 # We don't coerce to lists of single values, as some values must be unique 19 in types.either singleLdapValueType (types.listOf singleLdapValueType); 20 21 ldapAttrsType = 22 let 23 options = { 24 attrs = mkOption { 25 type = types.attrsOf ldapValueType; 26 default = {}; 27 description = "Attributes of the parent entry."; 28 }; 29 children = mkOption { 30 # Hide the child attributes, to avoid infinite recursion in e.g. documentation 31 # Actual Nix evaluation is lazy, so this is not an issue there 32 type = let 33 hiddenOptions = lib.mapAttrs (name: attr: attr // { visible = false; }) options; 34 in types.attrsOf (types.submodule { options = hiddenOptions; }); 35 default = {}; 36 description = "Child entries of the current entry, with recursively the same structure."; 37 example = lib.literalExample '' 38 { 39 "cn=schema" = { 40 # The attribute used in the DN must be defined 41 attrs = { cn = "schema"; }; 42 children = { 43 # This entry's DN is expanded to "cn=foo,cn=schema" 44 "cn=foo" = { ... }; 45 }; 46 # These includes are inserted after "cn=schema", but before "cn=foo,cn=schema" 47 includes = [ ... ]; 48 }; 49 } 50 ''; 51 }; 52 includes = mkOption { 53 type = types.listOf types.path; 54 default = []; 55 description = '' 56 LDIF files to include after the parent's attributes but before its children. 57 ''; 58 }; 59 }; 60 in types.submodule { inherit options; }; 61 62 valueToLdif = attr: values: let 63 listValues = if lib.isList values then values else lib.singleton values; 64 in map (value: 65 if lib.isAttrs value then 66 if lib.hasAttr "path" value 67 then "${attr}:< file://${value.path}" 68 else "${attr}:: ${value.base64}" 69 else "${attr}: ${lib.replaceStrings [ "\n" ] [ "\n " ] value}" 70 ) listValues; 71 72 attrsToLdif = dn: { attrs, children, includes, ... }: ['' 73 dn: ${dn} 74 ${lib.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList valueToLdif attrs))} 75 ''] ++ (map (path: "include: file://${path}\n") includes) ++ ( 76 lib.flatten (lib.mapAttrsToList (name: value: attrsToLdif "${name},${dn}" value) children) 77 ); 78in { 79 imports = let 80 deprecationNote = "This option is removed due to the deprecation of `slapd.conf` upstream. Please migrate to `services.openldap.settings`, see the release notes for advice with this process."; 81 mkDatabaseOption = old: new: 82 lib.mkChangedOptionModule [ "services" "openldap" old ] [ "services" "openldap" "settings" "children" ] 83 (config: let 84 database = lib.getAttrFromPath [ "services" "openldap" "database" ] config; 85 value = lib.getAttrFromPath [ "services" "openldap" old ] config; 86 in lib.setAttrByPath ([ "olcDatabase={1}${database}" "attrs" ] ++ new) value); 87 in [ 88 (lib.mkRemovedOptionModule [ "services" "openldap" "extraConfig" ] deprecationNote) 89 (lib.mkRemovedOptionModule [ "services" "openldap" "extraDatabaseConfig" ] deprecationNote) 90 91 (lib.mkChangedOptionModule [ "services" "openldap" "logLevel" ] [ "services" "openldap" "settings" "attrs" "olcLogLevel" ] 92 (config: lib.splitString " " (lib.getAttrFromPath [ "services" "openldap" "logLevel" ] config))) 93 (lib.mkChangedOptionModule [ "services" "openldap" "defaultSchemas" ] [ "services" "openldap" "settings" "children" "cn=schema" "includes"] 94 (config: lib.optionals (lib.getAttrFromPath [ "services" "openldap" "defaultSchemas" ] config) ( 95 map (schema: "${openldap}/etc/schema/${schema}.ldif") [ "core" "cosine" "inetorgperson" "nis" ]))) 96 97 (lib.mkChangedOptionModule [ "services" "openldap" "database" ] [ "services" "openldap" "settings" "children" ] 98 (config: let 99 database = lib.getAttrFromPath [ "services" "openldap" "database" ] config; 100 in { 101 "olcDatabase={1}${database}".attrs = { 102 # objectClass is case-insensitive, so don't need to capitalize ${database} 103 objectClass = [ "olcdatabaseconfig" "olc${database}config" ]; 104 olcDatabase = "{1}${database}"; 105 olcDbDirectory = lib.mkDefault "/var/db/openldap"; 106 }; 107 "cn=schema".includes = lib.mkDefault ( 108 map (schema: "${openldap}/etc/schema/${schema}.ldif") [ "core" "cosine" "inetorgperson" "nis" ] 109 ); 110 })) 111 (mkDatabaseOption "rootpwFile" [ "olcRootPW" "path" ]) 112 (mkDatabaseOption "suffix" [ "olcSuffix" ]) 113 (mkDatabaseOption "dataDir" [ "olcDbDirectory" ]) 114 (mkDatabaseOption "rootdn" [ "olcRootDN" ]) 115 (mkDatabaseOption "rootpw" [ "olcRootPW" ]) 116 ]; 117 options = { 118 services.openldap = { 119 enable = mkOption { 120 type = types.bool; 121 default = false; 122 description = " 123 Whether to enable the ldap server. 124 "; 125 }; 126 127 package = mkOption { 128 type = types.package; 129 default = pkgs.openldap; 130 description = '' 131 OpenLDAP package to use. 132 133 This can be used to, for example, set an OpenLDAP package 134 with custom overrides to enable modules or other 135 functionality. 136 ''; 137 }; 138 139 user = mkOption { 140 type = types.str; 141 default = "openldap"; 142 description = "User account under which slapd runs."; 143 }; 144 145 group = mkOption { 146 type = types.str; 147 default = "openldap"; 148 description = "Group account under which slapd runs."; 149 }; 150 151 urlList = mkOption { 152 type = types.listOf types.str; 153 default = [ "ldap:///" ]; 154 description = "URL list slapd should listen on."; 155 example = [ "ldaps:///" ]; 156 }; 157 158 settings = mkOption { 159 type = ldapAttrsType; 160 description = "Configuration for OpenLDAP, in OLC format"; 161 example = lib.literalExample '' 162 { 163 attrs.olcLogLevel = [ "stats" ]; 164 children = { 165 "cn=schema".includes = [ 166 "\${pkgs.openldap}/etc/schema/core.ldif" 167 "\${pkgs.openldap}/etc/schema/cosine.ldif" 168 "\${pkgs.openldap}/etc/schema/inetorgperson.ldif" 169 ]; 170 "olcDatabase={-1}frontend" = { 171 attrs = { 172 objectClass = "olcDatabaseConfig"; 173 olcDatabase = "{-1}frontend"; 174 olcAccess = [ "{0}to * by dn.exact=uidNumber=0+gidNumber=0,cn=peercred,cn=external,cn=auth manage stop by * none stop" ]; 175 }; 176 }; 177 "olcDatabase={0}config" = { 178 attrs = { 179 objectClass = "olcDatabaseConfig"; 180 olcDatabase = "{0}config"; 181 olcAccess = [ "{0}to * by * none break" ]; 182 }; 183 }; 184 "olcDatabase={1}mdb" = { 185 attrs = { 186 objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ]; 187 olcDatabase = "{1}mdb"; 188 olcDbDirectory = "/var/db/ldap"; 189 olcDbIndex = [ 190 "objectClass eq" 191 "cn pres,eq" 192 "uid pres,eq" 193 "sn pres,eq,subany" 194 ]; 195 olcSuffix = "dc=example,dc=com"; 196 olcAccess = [ "{0}to * by * read break" ]; 197 }; 198 }; 199 }; 200 }; 201 ''; 202 }; 203 204 # This option overrides settings 205 configDir = mkOption { 206 type = types.nullOr types.path; 207 default = null; 208 description = '' 209 Use this config directory instead of generating one from the 210 <literal>settings</literal> option. Overrides all NixOS settings. If 211 you use this option,ensure `olcPidFile` is set to `/run/slapd/slapd.conf`. 212 ''; 213 example = "/var/db/slapd.d"; 214 }; 215 216 declarativeContents = mkOption { 217 type = with types; attrsOf lines; 218 default = {}; 219 description = '' 220 Declarative contents for the LDAP database, in LDIF format by suffix. 221 222 All data will be erased when starting the LDAP server. Modifications 223 to the database are not prevented, they are just dropped on the next 224 reboot of the server. Performance-wise the database and indexes are 225 rebuilt on each server startup, so this will slow down server startup, 226 especially with large databases. 227 ''; 228 example = lib.literalExample '' 229 { 230 "dc=example,dc=org" = ''' 231 dn= dn: dc=example,dc=org 232 objectClass: domain 233 dc: example 234 235 dn: ou=users,dc=example,dc=org 236 objectClass = organizationalUnit 237 ou: users 238 239 # ... 240 '''; 241 } 242 ''; 243 }; 244 }; 245 }; 246 247 meta.maintainers = with lib.maintainers; [ mic92 kwohlfahrt ]; 248 249 config = mkIf cfg.enable { 250 assertions = map (opt: { 251 assertion = ((getAttr opt cfg) != "_mkMergedOptionModule") -> (cfg.database != "_mkMergedOptionModule"); 252 message = "Legacy OpenLDAP option `services.openldap.${opt}` requires `services.openldap.database` (use value \"mdb\" if unsure)"; 253 }) legacyOptions; 254 environment.systemPackages = [ openldap ]; 255 256 # Literal attributes must always be set 257 services.openldap.settings = { 258 attrs = { 259 objectClass = "olcGlobal"; 260 cn = "config"; 261 olcPidFile = "/run/slapd/slapd.pid"; 262 }; 263 children."cn=schema".attrs = { 264 cn = "schema"; 265 objectClass = "olcSchemaConfig"; 266 }; 267 }; 268 269 systemd.services.openldap = { 270 description = "LDAP server"; 271 wantedBy = [ "multi-user.target" ]; 272 after = [ "network.target" ]; 273 preStart = let 274 settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings)); 275 276 dbSettings = lib.filterAttrs (name: value: lib.hasPrefix "olcDatabase=" name) cfg.settings.children; 277 dataDirs = lib.mapAttrs' (name: value: lib.nameValuePair value.attrs.olcSuffix value.attrs.olcDbDirectory) 278 (lib.filterAttrs (_: value: value.attrs ? olcDbDirectory) dbSettings); 279 dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; 280 mkLoadScript = dn: let 281 dataDir = lib.escapeShellArg (getAttr dn dataDirs); 282 in '' 283 rm -rf ${dataDir}/* 284 ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -b ${dn} -l ${getAttr dn dataFiles} 285 chown -R "${cfg.user}:${cfg.group}" ${dataDir} 286 ''; 287 in '' 288 mkdir -p /run/slapd 289 chown -R "${cfg.user}:${cfg.group}" /run/slapd 290 291 mkdir -p ${lib.escapeShellArg configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)} 292 chown "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)} 293 294 ${lib.optionalString (cfg.configDir == null) ('' 295 rm -Rf ${configDir}/* 296 ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} 297 '')} 298 chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir} 299 300 ${lib.concatStrings (map mkLoadScript (lib.attrNames cfg.declarativeContents))} 301 ${openldap}/bin/slaptest -u -F ${lib.escapeShellArg configDir} 302 ''; 303 serviceConfig = { 304 ExecStart = lib.escapeShellArgs ([ 305 "${openldap}/libexec/slapd" "-u" cfg.user "-g" cfg.group "-F" configDir 306 "-h" (lib.concatStringsSep " " cfg.urlList) 307 ]); 308 Type = "forking"; 309 PIDFile = cfg.settings.attrs.olcPidFile; 310 }; 311 }; 312 313 users.users = lib.optionalAttrs (cfg.user == "openldap") { 314 openldap = { 315 group = cfg.group; 316 isSystemUser = true; 317 }; 318 }; 319 320 users.groups = lib.optionalAttrs (cfg.group == "openldap") { 321 openldap = {}; 322 }; 323 }; 324}