at 22.05-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.literalExpression '' 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 defaultText = literalExpression "pkgs.openldap"; 131 description = '' 132 OpenLDAP package to use. 133 134 This can be used to, for example, set an OpenLDAP package 135 with custom overrides to enable modules or other 136 functionality. 137 ''; 138 }; 139 140 user = mkOption { 141 type = types.str; 142 default = "openldap"; 143 description = "User account under which slapd runs."; 144 }; 145 146 group = mkOption { 147 type = types.str; 148 default = "openldap"; 149 description = "Group account under which slapd runs."; 150 }; 151 152 urlList = mkOption { 153 type = types.listOf types.str; 154 default = [ "ldap:///" ]; 155 description = "URL list slapd should listen on."; 156 example = [ "ldaps:///" ]; 157 }; 158 159 settings = mkOption { 160 type = ldapAttrsType; 161 description = "Configuration for OpenLDAP, in OLC format"; 162 example = lib.literalExpression '' 163 { 164 attrs.olcLogLevel = [ "stats" ]; 165 children = { 166 "cn=schema".includes = [ 167 "''${pkgs.openldap}/etc/schema/core.ldif" 168 "''${pkgs.openldap}/etc/schema/cosine.ldif" 169 "''${pkgs.openldap}/etc/schema/inetorgperson.ldif" 170 ]; 171 "olcDatabase={-1}frontend" = { 172 attrs = { 173 objectClass = "olcDatabaseConfig"; 174 olcDatabase = "{-1}frontend"; 175 olcAccess = [ "{0}to * by dn.exact=uidNumber=0+gidNumber=0,cn=peercred,cn=external,cn=auth manage stop by * none stop" ]; 176 }; 177 }; 178 "olcDatabase={0}config" = { 179 attrs = { 180 objectClass = "olcDatabaseConfig"; 181 olcDatabase = "{0}config"; 182 olcAccess = [ "{0}to * by * none break" ]; 183 }; 184 }; 185 "olcDatabase={1}mdb" = { 186 attrs = { 187 objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ]; 188 olcDatabase = "{1}mdb"; 189 olcDbDirectory = "/var/db/ldap"; 190 olcDbIndex = [ 191 "objectClass eq" 192 "cn pres,eq" 193 "uid pres,eq" 194 "sn pres,eq,subany" 195 ]; 196 olcSuffix = "dc=example,dc=com"; 197 olcAccess = [ "{0}to * by * read break" ]; 198 }; 199 }; 200 }; 201 }; 202 ''; 203 }; 204 205 # This option overrides settings 206 configDir = mkOption { 207 type = types.nullOr types.path; 208 default = null; 209 description = '' 210 Use this config directory instead of generating one from the 211 <literal>settings</literal> option. Overrides all NixOS settings. If 212 you use this option,ensure `olcPidFile` is set to `/run/slapd/slapd.conf`. 213 ''; 214 example = "/var/db/slapd.d"; 215 }; 216 217 declarativeContents = mkOption { 218 type = with types; attrsOf lines; 219 default = {}; 220 description = '' 221 Declarative contents for the LDAP database, in LDIF format by suffix. 222 223 All data will be erased when starting the LDAP server. Modifications 224 to the database are not prevented, they are just dropped on the next 225 reboot of the server. Performance-wise the database and indexes are 226 rebuilt on each server startup, so this will slow down server startup, 227 especially with large databases. 228 ''; 229 example = lib.literalExpression '' 230 { 231 "dc=example,dc=org" = ''' 232 dn= dn: dc=example,dc=org 233 objectClass: domain 234 dc: example 235 236 dn: ou=users,dc=example,dc=org 237 objectClass = organizationalUnit 238 ou: users 239 240 # ... 241 '''; 242 } 243 ''; 244 }; 245 }; 246 }; 247 248 meta.maintainers = with lib.maintainers; [ mic92 kwohlfahrt ]; 249 250 config = mkIf cfg.enable { 251 assertions = map (opt: { 252 assertion = ((getAttr opt cfg) != "_mkMergedOptionModule") -> (cfg.database != "_mkMergedOptionModule"); 253 message = "Legacy OpenLDAP option `services.openldap.${opt}` requires `services.openldap.database` (use value \"mdb\" if unsure)"; 254 }) legacyOptions; 255 environment.systemPackages = [ openldap ]; 256 257 # Literal attributes must always be set 258 services.openldap.settings = { 259 attrs = { 260 objectClass = "olcGlobal"; 261 cn = "config"; 262 olcPidFile = "/run/slapd/slapd.pid"; 263 }; 264 children."cn=schema".attrs = { 265 cn = "schema"; 266 objectClass = "olcSchemaConfig"; 267 }; 268 }; 269 270 systemd.services.openldap = { 271 description = "LDAP server"; 272 wantedBy = [ "multi-user.target" ]; 273 after = [ "network.target" ]; 274 preStart = let 275 settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings)); 276 277 dbSettings = lib.filterAttrs (name: value: lib.hasPrefix "olcDatabase=" name) cfg.settings.children; 278 dataDirs = lib.mapAttrs' (name: value: lib.nameValuePair value.attrs.olcSuffix value.attrs.olcDbDirectory) 279 (lib.filterAttrs (_: value: value.attrs ? olcDbDirectory) dbSettings); 280 dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; 281 mkLoadScript = dn: let 282 dataDir = lib.escapeShellArg (getAttr dn dataDirs); 283 in '' 284 rm -rf ${dataDir}/* 285 ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -b ${dn} -l ${getAttr dn dataFiles} 286 chown -R "${cfg.user}:${cfg.group}" ${dataDir} 287 ''; 288 in '' 289 mkdir -p /run/slapd 290 chown -R "${cfg.user}:${cfg.group}" /run/slapd 291 292 mkdir -p ${lib.escapeShellArg configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)} 293 chown "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)} 294 295 ${lib.optionalString (cfg.configDir == null) ('' 296 rm -Rf ${configDir}/* 297 ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} 298 '')} 299 chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir} 300 301 ${lib.concatStrings (map mkLoadScript (lib.attrNames cfg.declarativeContents))} 302 ${openldap}/bin/slaptest -u -F ${lib.escapeShellArg configDir} 303 ''; 304 serviceConfig = { 305 ExecStart = lib.escapeShellArgs ([ 306 "${openldap}/libexec/slapd" "-u" cfg.user "-g" cfg.group "-F" configDir 307 "-h" (lib.concatStringsSep " " cfg.urlList) 308 ]); 309 Type = "forking"; 310 PIDFile = cfg.settings.attrs.olcPidFile; 311 }; 312 }; 313 314 users.users = lib.optionalAttrs (cfg.user == "openldap") { 315 openldap = { 316 group = cfg.group; 317 isSystemUser = true; 318 }; 319 }; 320 321 users.groups = lib.optionalAttrs (cfg.group == "openldap") { 322 openldap = {}; 323 }; 324 }; 325}