33with lib ;
44let
55 cfg = config . services . openldap ;
6- legacyOptions = [ "rootpwFile" "suffix" "dataDir" "rootdn" "rootpw" ] ;
76 openldap = cfg . package ;
87 configDir = if cfg . configDir != null then cfg . configDir else "/etc/openldap/slapd.d" ;
98
109 ldapValueType = let
1110 # Can't do types.either with multiple non-overlapping submodules, so define our own
1211 singleLdapValueType = lib . mkOptionType rec {
1312 name = "LDAP" ;
14- description = "LDAP value" ;
13+ # TODO: It would be nice to define a { secret = ...; } option, using
14+ # systemd's LoadCredentials for secrets. That would remove the last
15+ # barrier to using DynamicUser for openldap. This is blocked on
16+ # systemd/systemd#19604
17+ description = ''
18+ LDAP value - either a string, or an attrset containing
19+ <literal>path</literal> or <literal>base64</literal> for included
20+ values or base-64 encoded values respectively.
21+ '' ;
1522 check = x : lib . isString x || ( lib . isAttrs x && ( x ? path || x ? base64 ) ) ;
1623 merge = lib . mergeEqualOption ;
1724 } ;
7683 lib . flatten ( lib . mapAttrsToList ( name : value : attrsToLdif "${ name } ,${ dn } " value ) children )
7784 ) ;
7885in {
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- ] ;
11786 options = {
11887 services . openldap = {
11988 enable = mkOption {
12089 type = types . bool ;
12190 default = false ;
122- description = "
123- Whether to enable the ldap server.
124- " ;
91+ description = "Whether to enable the ldap server." ;
12592 } ;
12693
12794 package = mkOption {
186153 attrs = {
187154 objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
188155 olcDatabase = "{1}mdb";
189- olcDbDirectory = "/var/db /ldap";
156+ olcDbDirectory = "/var/lib/openldap /ldap";
190157 olcDbIndex = [
191158 "objectClass eq"
192159 "cn pres,eq"
@@ -208,10 +175,20 @@ in {
208175 default = null ;
209176 description = ''
210177 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`.
178+ <literal>settings</literal> option. Overrides all NixOS settings.
179+ '' ;
180+ example = "/var/lib/openldap/slapd.d" ;
181+ } ;
182+
183+ mutableConfig = mkOption {
184+ type = types . bool ;
185+ default = false ;
186+ description = ''
187+ Whether to allow writable on-line configuration. If
188+ <literal>true</literal>, the NixOS settings will only be used to
189+ initialize the OpenLDAP configuration if it does not exist, and are
190+ subsequently ignored.
213191 '' ;
214- example = "/var/db/slapd.d" ;
215192 } ;
216193
217194 declarativeContents = mkOption {
@@ -225,6 +202,11 @@ in {
225202 reboot of the server. Performance-wise the database and indexes are
226203 rebuilt on each server startup, so this will slow down server startup,
227204 especially with large databases.
205+
206+ Note that the root of the DB must be defined in
207+ `services.openldap.settings` and the
208+ `olcDbDirectory` must begin with
209+ `"/var/lib/openldap"`.
228210 '' ;
229211 example = lib . literalExpression ''
230212 {
@@ -247,19 +229,61 @@ in {
247229
248230 meta . maintainers = with lib . maintainers ; [ kwohlfahrt ] ;
249231
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 ;
232+ config = let
233+ dbSettings = mapAttrs' ( name : { attrs , ... } : nameValuePair attrs . olcSuffix attrs )
234+ ( filterAttrs ( name : { attrs , ... } : ( hasPrefix "olcDatabase=" name ) && attrs ? olcSuffix ) cfg . settings . children ) ;
235+ settingsFile = pkgs . writeText "config.ldif" ( lib . concatStringsSep "\n " ( attrsToLdif "cn=config" cfg . settings ) ) ;
236+ writeConfig = pkgs . writeShellScript "openldap-config" ''
237+ set -euo pipefail
238+
239+ ${ lib . optionalString ( ! cfg . mutableConfig ) ''
240+ chmod -R u+w ${ configDir }
241+ rm -rf ${ configDir } /*
242+ '' }
243+ if [ ! -e "${ configDir } /cn=config.ldif" ]; then
244+ ${ openldap } /bin/slapadd -F ${ configDir } -bcn=config -l ${ settingsFile }
245+ fi
246+ chmod -R ${ if cfg . mutableConfig then "u+rw" else "u+r-w" } ${ configDir }
247+ '' ;
248+
249+ contentsFiles = mapAttrs ( dn : ldif : pkgs . writeText "${ dn } .ldif" ldif ) cfg . declarativeContents ;
250+ writeContents = pkgs . writeShellScript "openldap-load" ''
251+ set -euo pipefail
252+
253+ rm -rf $2/*
254+ ${ openldap } /bin/slapadd -F ${ configDir } -b $1 -l $3
255+ '' ;
256+ in mkIf cfg . enable {
257+ assertions = [ {
258+ assertion = ( cfg . declarativeContents != { } ) -> cfg . configDir == null ;
259+ message = ''
260+ Declarative DB contents (${ attrNames cfg . declarativeContents } ) are not
261+ supported with user-managed configuration.
262+ '' ;
263+ } ] ++ ( map ( dn : {
264+ assertion = ( getAttr dn dbSettings ) ? "olcDbDirectory" ;
265+ # olcDbDirectory is necessary to prepopulate database using `slapadd`.
266+ message = ''
267+ Declarative DB ${ dn } does not exist in `services.openldap.settings`, or does not have
268+ `olcDbDirectory` configured.
269+ '' ;
270+ } ) ( attrNames cfg . declarativeContents ) ) ++ ( mapAttrsToList ( dn : { olcDbDirectory ? null , ... } : {
271+ # For forward compatibility with `DynamicUser`, and to avoid accidentally clobbering
272+ # directories with `declarativeContents`.
273+ assertion = ( olcDbDirectory != null ) ->
274+ ( ( hasPrefix "/var/lib/openldap/" olcDbDirectory ) && ( olcDbDirectory != "/var/lib/openldap/" ) ) ;
275+ message = ''
276+ Database ${ dn } has `olcDbDirectory` (${ olcDbDirectory } ) that is not a subdirectory of
277+ `/var/lib/openldap/`.
278+ '' ;
279+ } ) dbSettings ) ;
255280 environment . systemPackages = [ openldap ] ;
256281
257282 # Literal attributes must always be set
258283 services . openldap . settings = {
259284 attrs = {
260285 objectClass = "olcGlobal" ;
261286 cn = "config" ;
262- olcPidFile = "/run/slapd/slapd.pid" ;
263287 } ;
264288 children . "cn=schema" . attrs = {
265289 cn = "schema" ;
@@ -276,44 +300,31 @@ in {
276300 ] ;
277301 wantedBy = [ "multi-user.target" ] ;
278302 after = [ "network-online.target" ] ;
279- preStart = let
280- settingsFile = pkgs . writeText "config.ldif" ( lib . concatStringsSep "\n " ( attrsToLdif "cn=config" cfg . settings ) ) ;
281-
282- dbSettings = lib . filterAttrs ( name : value : lib . hasPrefix "olcDatabase=" name ) cfg . settings . children ;
283- dataDirs = lib . mapAttrs' ( name : value : lib . nameValuePair value . attrs . olcSuffix value . attrs . olcDbDirectory )
284- ( lib . filterAttrs ( _ : value : value . attrs ? olcDbDirectory ) dbSettings ) ;
285- dataFiles = lib . mapAttrs ( dn : contents : pkgs . writeText "${ dn } .ldif" contents ) cfg . declarativeContents ;
286- mkLoadScript = dn : let
287- dataDir = lib . escapeShellArg ( getAttr dn dataDirs ) ;
288- in ''
289- rm -rf ${ dataDir } /*
290- ${ openldap } /bin/slapadd -F ${ lib . escapeShellArg configDir } -b ${ dn } -l ${ getAttr dn dataFiles }
291- chown -R "${ cfg . user } :${ cfg . group } " ${ dataDir }
292- '' ;
293- in ''
294- mkdir -p /run/slapd
295- chown -R "${ cfg . user } :${ cfg . group } " /run/slapd
296-
297- mkdir -p ${ lib . escapeShellArg configDir } ${ lib . escapeShellArgs ( lib . attrValues dataDirs ) }
298- chown "${ cfg . user } :${ cfg . group } " ${ lib . escapeShellArg configDir } ${ lib . escapeShellArgs ( lib . attrValues dataDirs ) }
299-
300- ${ lib . optionalString ( cfg . configDir == null ) ( ''
301- rm -Rf ${ configDir } /*
302- ${ openldap } /bin/slapadd -F ${ configDir } -bcn=config -l ${ settingsFile }
303- '' ) }
304- chown -R "${ cfg . user } :${ cfg . group } " ${ lib . escapeShellArg configDir }
305-
306- ${ lib . concatStrings ( map mkLoadScript ( lib . attrNames cfg . declarativeContents ) ) }
307- ${ openldap } /bin/slaptest -u -F ${ lib . escapeShellArg configDir }
308- '' ;
309303 serviceConfig = {
304+ User = cfg . user ;
305+ Group = cfg . group ;
306+ ExecStartPre = [
307+ "!${ pkgs . coreutils } /bin/mkdir -p ${ configDir } "
308+ "+${ pkgs . coreutils } /bin/chown $USER ${ configDir } "
309+ ] ++ ( lib . optional ( cfg . configDir == null ) writeConfig )
310+ ++ ( mapAttrsToList ( dn : content : lib . escapeShellArgs [
311+ writeContents dn ( getAttr dn dbSettings ) . olcDbDirectory content
312+ ] ) contentsFiles )
313+ ++ [ "${ openldap } /bin/slaptest -u -F ${ configDir } " ] ;
310314 ExecStart = lib . escapeShellArgs ( [
311- "${ openldap } /libexec/slapd" "-u" cfg . user "-g" cfg . group "-F" configDir
312- "-h" ( lib . concatStringsSep " " cfg . urlList )
315+ "${ openldap } /libexec/slapd" "-d" "0" "-F" configDir "-h" ( lib . concatStringsSep " " cfg . urlList )
313316 ] ) ;
314317 Type = "notify" ;
318+ # Fixes an error where openldap attempts to notify from a thread
319+ # outside the main process:
320+ # Got notification message from PID 6378, but reception only permitted for main PID 6377
315321 NotifyAccess = "all" ;
316- PIDFile = cfg . settings . attrs . olcPidFile ;
322+ RuntimeDirectory = "openldap" ;
323+ StateDirectory = [ "openldap" ]
324+ ++ ( map ( { olcDbDirectory , ... } : removePrefix "/var/lib/" olcDbDirectory ) ( attrValues dbSettings ) ) ;
325+ StateDirectoryMode = "700" ;
326+ AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ] ;
327+ CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ] ;
317328 } ;
318329 } ;
319330
0 commit comments