diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix
index d80d1b07b97c3..5006a3065e940 100644
--- a/nixos/modules/services/databases/openldap.nix
+++ b/nixos/modules/services/databases/openldap.nix
@@ -3,7 +3,6 @@
with lib;
let
cfg = config.services.openldap;
- legacyOptions = [ "rootpwFile" "suffix" "dataDir" "rootdn" "rootpw" ];
openldap = cfg.package;
configDir = if cfg.configDir != null then cfg.configDir else "/etc/openldap/slapd.d";
@@ -11,7 +10,15 @@ let
# Can't do types.either with multiple non-overlapping submodules, so define our own
singleLdapValueType = lib.mkOptionType rec {
name = "LDAP";
- description = "LDAP value";
+ # TODO: It would be nice to define a { secret = ...; } option, using
+ # systemd's LoadCredentials for secrets. That would remove the last
+ # barrier to using DynamicUser for openldap. This is blocked on
+ # systemd/systemd#19604
+ description = ''
+ LDAP value - either a string, or an attrset containing
+ path or base64 for included
+ values or base-64 encoded values respectively.
+ '';
check = x: lib.isString x || (lib.isAttrs x && (x ? path || x ? base64));
merge = lib.mergeEqualOption;
};
@@ -76,52 +83,12 @@ let
lib.flatten (lib.mapAttrsToList (name: value: attrsToLdif "${name},${dn}" value) children)
);
in {
- imports = let
- 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.";
- mkDatabaseOption = old: new:
- lib.mkChangedOptionModule [ "services" "openldap" old ] [ "services" "openldap" "settings" "children" ]
- (config: let
- database = lib.getAttrFromPath [ "services" "openldap" "database" ] config;
- value = lib.getAttrFromPath [ "services" "openldap" old ] config;
- in lib.setAttrByPath ([ "olcDatabase={1}${database}" "attrs" ] ++ new) value);
- in [
- (lib.mkRemovedOptionModule [ "services" "openldap" "extraConfig" ] deprecationNote)
- (lib.mkRemovedOptionModule [ "services" "openldap" "extraDatabaseConfig" ] deprecationNote)
-
- (lib.mkChangedOptionModule [ "services" "openldap" "logLevel" ] [ "services" "openldap" "settings" "attrs" "olcLogLevel" ]
- (config: lib.splitString " " (lib.getAttrFromPath [ "services" "openldap" "logLevel" ] config)))
- (lib.mkChangedOptionModule [ "services" "openldap" "defaultSchemas" ] [ "services" "openldap" "settings" "children" "cn=schema" "includes"]
- (config: lib.optionals (lib.getAttrFromPath [ "services" "openldap" "defaultSchemas" ] config) (
- map (schema: "${openldap}/etc/schema/${schema}.ldif") [ "core" "cosine" "inetorgperson" "nis" ])))
-
- (lib.mkChangedOptionModule [ "services" "openldap" "database" ] [ "services" "openldap" "settings" "children" ]
- (config: let
- database = lib.getAttrFromPath [ "services" "openldap" "database" ] config;
- in {
- "olcDatabase={1}${database}".attrs = {
- # objectClass is case-insensitive, so don't need to capitalize ${database}
- objectClass = [ "olcdatabaseconfig" "olc${database}config" ];
- olcDatabase = "{1}${database}";
- olcDbDirectory = lib.mkDefault "/var/db/openldap";
- };
- "cn=schema".includes = lib.mkDefault (
- map (schema: "${openldap}/etc/schema/${schema}.ldif") [ "core" "cosine" "inetorgperson" "nis" ]
- );
- }))
- (mkDatabaseOption "rootpwFile" [ "olcRootPW" "path" ])
- (mkDatabaseOption "suffix" [ "olcSuffix" ])
- (mkDatabaseOption "dataDir" [ "olcDbDirectory" ])
- (mkDatabaseOption "rootdn" [ "olcRootDN" ])
- (mkDatabaseOption "rootpw" [ "olcRootPW" ])
- ];
options = {
services.openldap = {
enable = mkOption {
type = types.bool;
default = false;
- description = "
- Whether to enable the ldap server.
- ";
+ description = "Whether to enable the ldap server.";
};
package = mkOption {
@@ -186,7 +153,7 @@ in {
attrs = {
objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
olcDatabase = "{1}mdb";
- olcDbDirectory = "/var/db/ldap";
+ olcDbDirectory = "/var/lib/openldap/ldap";
olcDbIndex = [
"objectClass eq"
"cn pres,eq"
@@ -208,10 +175,20 @@ in {
default = null;
description = ''
Use this config directory instead of generating one from the
- settings option. Overrides all NixOS settings. If
- you use this option,ensure `olcPidFile` is set to `/run/slapd/slapd.conf`.
+ settings option. Overrides all NixOS settings.
+ '';
+ example = "/var/lib/openldap/slapd.d";
+ };
+
+ mutableConfig = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Whether to allow writable on-line configuration. If
+ true, the NixOS settings will only be used to
+ initialize the OpenLDAP configuration if it does not exist, and are
+ subsequently ignored.
'';
- example = "/var/db/slapd.d";
};
declarativeContents = mkOption {
@@ -225,6 +202,11 @@ in {
reboot of the server. Performance-wise the database and indexes are
rebuilt on each server startup, so this will slow down server startup,
especially with large databases.
+
+ Note that the root of the DB must be defined in
+ services.openldap.settings and the
+ olcDbDirectory must begin with
+ "/var/lib/openldap".
'';
example = lib.literalExpression ''
{
@@ -247,11 +229,54 @@ in {
meta.maintainers = with lib.maintainers; [ mic92 kwohlfahrt ];
- config = mkIf cfg.enable {
- assertions = map (opt: {
- assertion = ((getAttr opt cfg) != "_mkMergedOptionModule") -> (cfg.database != "_mkMergedOptionModule");
- message = "Legacy OpenLDAP option `services.openldap.${opt}` requires `services.openldap.database` (use value \"mdb\" if unsure)";
- }) legacyOptions;
+ config = let
+ dbSettings = mapAttrs' (name: { attrs, ... }: nameValuePair attrs.olcSuffix attrs)
+ (filterAttrs (name: { attrs, ... }: (hasPrefix "olcDatabase=" name) && attrs ? olcSuffix) cfg.settings.children);
+ settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings));
+ writeConfig = pkgs.writeShellScript "openldap-config" ''
+ set -euo pipefail
+
+ ${lib.optionalString (!cfg.mutableConfig) ''
+ chmod -R u+w ${configDir}
+ rm -rf ${configDir}/*
+ ''}
+ if [ ! -e "${configDir}/cn=config.ldif" ]; then
+ ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile}
+ fi
+ chmod -R ${if cfg.mutableConfig then "u+rw" else "u+r-w"} ${configDir}
+ '';
+
+ contentsFiles = mapAttrs (dn: ldif: pkgs.writeText "${dn}.ldif" ldif) cfg.declarativeContents;
+ writeContents = pkgs.writeShellScript "openldap-load" ''
+ set -euo pipefail
+
+ rm -rf $2/*
+ ${openldap}/bin/slapadd -F ${configDir} -b $1 -l $3
+ '';
+ in mkIf cfg.enable {
+ assertions = [{
+ assertion = (cfg.declarativeContents != {}) -> cfg.configDir == null;
+ message = ''
+ Declarative DB contents (${attrNames cfg.declarativeContents}) are not
+ supported with user-managed configuration.
+ '';
+ }] ++ (map (dn: {
+ assertion = (getAttr dn dbSettings) ? "olcDbDirectory";
+ # olcDbDirectory is necessary to prepopulate database using `slapadd`.
+ message = ''
+ Declarative DB ${dn} does not exist in `services.openldap.settings`, or does not have
+ `olcDbDirectory` configured.
+ '';
+ }) (attrNames cfg.declarativeContents)) ++ (mapAttrsToList (dn: { olcDbDirectory ? null, ... }: {
+ # For forward compatibility with `DynamicUser`, and to avoid accidentally clobbering
+ # directories with `declarativeContents`.
+ assertion = (olcDbDirectory != null) ->
+ ((hasPrefix "/var/lib/openldap/" olcDbDirectory) && (olcDbDirectory != "/var/lib/openldap/"));
+ message = ''
+ Database ${dn} has `olcDbDirectory` (${olcDbDirectory}) that is not a subdirectory of
+ `/var/lib/openldap/`.
+ '';
+ }) dbSettings);
environment.systemPackages = [ openldap ];
# Literal attributes must always be set
@@ -259,7 +284,6 @@ in {
attrs = {
objectClass = "olcGlobal";
cn = "config";
- olcPidFile = "/run/slapd/slapd.pid";
};
children."cn=schema".attrs = {
cn = "schema";
@@ -276,44 +300,31 @@ in {
];
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
- preStart = let
- settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings));
-
- dbSettings = lib.filterAttrs (name: value: lib.hasPrefix "olcDatabase=" name) cfg.settings.children;
- dataDirs = lib.mapAttrs' (name: value: lib.nameValuePair value.attrs.olcSuffix value.attrs.olcDbDirectory)
- (lib.filterAttrs (_: value: value.attrs ? olcDbDirectory) dbSettings);
- dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents;
- mkLoadScript = dn: let
- dataDir = lib.escapeShellArg (getAttr dn dataDirs);
- in ''
- rm -rf ${dataDir}/*
- ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -b ${dn} -l ${getAttr dn dataFiles}
- chown -R "${cfg.user}:${cfg.group}" ${dataDir}
- '';
- in ''
- mkdir -p /run/slapd
- chown -R "${cfg.user}:${cfg.group}" /run/slapd
-
- mkdir -p ${lib.escapeShellArg configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)}
- chown "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)}
-
- ${lib.optionalString (cfg.configDir == null) (''
- rm -Rf ${configDir}/*
- ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile}
- '')}
- chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir}
-
- ${lib.concatStrings (map mkLoadScript (lib.attrNames cfg.declarativeContents))}
- ${openldap}/bin/slaptest -u -F ${lib.escapeShellArg configDir}
- '';
serviceConfig = {
+ User = cfg.user;
+ Group = cfg.group;
+ ExecStartPre = [
+ "!${pkgs.coreutils}/bin/mkdir -p ${configDir}"
+ "+${pkgs.coreutils}/bin/chown $USER ${configDir}"
+ ] ++ (lib.optional (cfg.configDir == null) writeConfig)
+ ++ (mapAttrsToList (dn: content: lib.escapeShellArgs [
+ writeContents dn (getAttr dn dbSettings).olcDbDirectory content
+ ]) contentsFiles)
+ ++ [ "${openldap}/bin/slaptest -u -F ${configDir}" ];
ExecStart = lib.escapeShellArgs ([
- "${openldap}/libexec/slapd" "-u" cfg.user "-g" cfg.group "-F" configDir
- "-h" (lib.concatStringsSep " " cfg.urlList)
+ "${openldap}/libexec/slapd" "-d" "0" "-F" configDir "-h" (lib.concatStringsSep " " cfg.urlList)
]);
Type = "notify";
+ # Fixes an error where openldap attempts to notify from a thread
+ # outside the main process:
+ # Got notification message from PID 6378, but reception only permitted for main PID 6377
NotifyAccess = "all";
- PIDFile = cfg.settings.attrs.olcPidFile;
+ RuntimeDirectory = "openldap";
+ StateDirectory = ["openldap"]
+ ++ (map ({olcDbDirectory, ... }: removePrefix "/var/lib/" olcDbDirectory) (attrValues dbSettings));
+ StateDirectoryMode = "700";
+ AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];
+ CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ];
};
};
diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix
index 3c388119d5d24..04e2650e38080 100644
--- a/nixos/tests/openldap.nix
+++ b/nixos/tests/openldap.nix
@@ -1,9 +1,4 @@
-{ pkgs ? (import ../.. { inherit system; config = { }; })
-, system ? builtins.currentSystem
-, ...
-}:
-
-let
+import ./make-test-python.nix ({ pkgs, ... }: let
dbContents = ''
dn: dc=example
objectClass: domain
@@ -13,118 +8,136 @@ let
objectClass: organizationalUnit
ou: users
'';
- testScript = ''
- machine.wait_for_unit("openldap.service")
- machine.succeed(
- 'ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"',
- )
+
+ ldifConfig = ''
+ dn: cn=config
+ cn: config
+ objectClass: olcGlobal
+ olcLogLevel: stats
+
+ dn: cn=schema,cn=config
+ cn: schema
+ objectClass: olcSchemaConfig
+
+ include: file://${pkgs.openldap}/etc/schema/core.ldif
+ include: file://${pkgs.openldap}/etc/schema/cosine.ldif
+ include: file://${pkgs.openldap}/etc/schema/inetorgperson.ldif
+
+ dn: olcDatabase={0}config,cn=config
+ olcDatabase: {0}config
+ objectClass: olcDatabaseConfig
+ olcRootDN: cn=root,cn=config
+ olcRootPW: configpassword
+
+ dn: olcDatabase={1}mdb,cn=config
+ objectClass: olcDatabaseConfig
+ objectClass: olcMdbConfig
+ olcDatabase: {1}mdb
+ olcDbDirectory: /var/db/openldap
+ olcDbIndex: objectClass eq
+ olcSuffix: dc=example
+ olcRootDN: cn=root,dc=example
+ olcRootPW: notapassword
'';
in {
- # New-style configuration
- current = import ./make-test-python.nix ({ pkgs, ... }: {
- inherit testScript;
- name = "openldap";
+ name = "openldap";
- nodes.machine = { pkgs, ... }: {
- environment.etc."openldap/root_password".text = "notapassword";
- services.openldap = {
- enable = true;
- settings = {
- children = {
- "cn=schema".includes = [
- "${pkgs.openldap}/etc/schema/core.ldif"
- "${pkgs.openldap}/etc/schema/cosine.ldif"
- "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
- "${pkgs.openldap}/etc/schema/nis.ldif"
- ];
- "olcDatabase={1}mdb" = {
- # This tests string, base64 and path values, as well as lists of string values
- attrs = {
- objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
- olcDatabase = "{1}mdb";
- olcDbDirectory = "/var/db/openldap";
- olcSuffix = "dc=example";
- olcRootDN = {
- # cn=root,dc=example
- base64 = "Y249cm9vdCxkYz1leGFtcGxl";
- };
- olcRootPW = {
- path = "/etc/openldap/root_password";
- };
+ nodes.machine = { pkgs, ... }: {
+ environment.etc."openldap/root_password".text = "notapassword";
+ services.openldap = {
+ enable = true;
+ urlList = [ "ldapi:///" "ldap://" ];
+ settings = {
+ children = {
+ "cn=schema".includes = [
+ "${pkgs.openldap}/etc/schema/core.ldif"
+ "${pkgs.openldap}/etc/schema/cosine.ldif"
+ "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
+ "${pkgs.openldap}/etc/schema/nis.ldif"
+ ];
+ "olcDatabase={0}config" = {
+ attrs = {
+ objectClass = [ "olcDatabaseConfig" ];
+ olcDatabase = "{0}config";
+ olcRootDN = "cn=root,cn=config";
+ olcRootPW = "configpassword";
+ };
+ };
+ "olcDatabase={1}mdb" = {
+ # This tests string, base64 and path values, as well as lists of string values
+ attrs = {
+ objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
+ olcDatabase = "{1}mdb";
+ olcDbDirectory = "/var/lib/openldap/db";
+ olcSuffix = "dc=example";
+ olcRootDN = {
+ # cn=root,dc=example
+ base64 = "Y249cm9vdCxkYz1leGFtcGxl";
+ };
+ olcRootPW = {
+ path = "/etc/openldap/root_password";
};
};
};
};
- declarativeContents."dc=example" = dbContents;
};
};
- }) { inherit pkgs system; };
-
- # Old-style configuration
- oldOptions = import ./make-test-python.nix ({ pkgs, ... }: {
- inherit testScript;
- name = "openldap";
- nodes.machine = { pkgs, ... }: {
- services.openldap = {
- enable = true;
- logLevel = "stats acl";
- defaultSchemas = true;
- database = "mdb";
- suffix = "dc=example";
- rootdn = "cn=root,dc=example";
- rootpw = "notapassword";
- declarativeContents."dc=example" = dbContents;
+ specialisation = {
+ declarativeContents.configuration = { ... }: {
+ services.openldap.declarativeContents."dc=example" = dbContents;
};
- };
- }) { inherit system pkgs; };
-
- # Manually managed configDir, for example if dynamic config is essential
- manualConfigDir = import ./make-test-python.nix ({ pkgs, ... }: {
- name = "openldap";
-
- nodes.machine = { pkgs, ... }: {
- services.openldap = {
- enable = true;
- configDir = "/var/db/slapd.d";
+ mutableConfig.configuration = { ... }: {
+ services.openldap = {
+ declarativeContents."dc=example" = dbContents;
+ mutableConfig = true;
+ };
+ };
+ manualConfigDir = {
+ inheritParentConfig = false;
+ configuration = { ... }: {
+ services.openldap = {
+ enable = true;
+ configDir = "/var/db/slapd.d";
+ };
+ };
};
};
+ };
+ testScript = { nodes, ... }: let
+ specializations = "${nodes.machine.config.system.build.toplevel}/specialisation";
+ changeRootPw = ''
+ dn: olcDatabase={1}mdb,cn=config
+ changetype: modify
+ replace: olcRootPW
+ olcRootPW: foobar
+ '';
+ in ''
+ # Test startup with empty DB
+ machine.wait_for_unit("openldap.service")
- testScript = let
- contents = pkgs.writeText "data.ldif" dbContents;
- config = pkgs.writeText "config.ldif" ''
- dn: cn=config
- cn: config
- objectClass: olcGlobal
- olcLogLevel: stats
- olcPidFile: /run/slapd/slapd.pid
-
- dn: cn=schema,cn=config
- cn: schema
- objectClass: olcSchemaConfig
+ with subtest("declarative contents"):
+ machine.succeed('${specializations}/declarativeContents/bin/switch-to-configuration test')
+ machine.wait_for_unit("openldap.service")
+ machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"')
+ machine.fail('ldapmodify -D cn=root,cn=config -w configpassword -f ${pkgs.writeText "rootpw.ldif" changeRootPw}')
- include: file://${pkgs.openldap}/etc/schema/core.ldif
- include: file://${pkgs.openldap}/etc/schema/cosine.ldif
- include: file://${pkgs.openldap}/etc/schema/inetorgperson.ldif
+ with subtest("mutable config"):
+ machine.succeed('${specializations}/mutableConfig/bin/switch-to-configuration test')
+ machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"')
+ machine.succeed('ldapmodify -D cn=root,cn=config -w configpassword -f ${pkgs.writeText "rootpw.ldif" changeRootPw}')
+ machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w foobar -b "dc=example"')
- dn: olcDatabase={1}mdb,cn=config
- objectClass: olcDatabaseConfig
- objectClass: olcMdbConfig
- olcDatabase: {1}mdb
- olcDbDirectory: /var/db/openldap
- olcDbIndex: objectClass eq
- olcSuffix: dc=example
- olcRootDN: cn=root,dc=example
- olcRootPW: notapassword
- '';
- in ''
+ with subtest("manual config dir"):
machine.succeed(
- "mkdir -p /var/db/slapd.d /var/db/openldap",
- "slapadd -F /var/db/slapd.d -n0 -l ${config}",
- "slapadd -F /var/db/slapd.d -n1 -l ${contents}",
- "chown -R openldap:openldap /var/db/slapd.d /var/db/openldap",
- "systemctl restart openldap",
+ 'mkdir /var/db/slapd.d /var/db/openldap',
+ 'slapadd -F /var/db/slapd.d -n0 -l ${pkgs.writeText "config.ldif" ldifConfig}',
+ 'slapadd -F /var/db/slapd.d -n1 -l ${pkgs.writeText "contents.ldif" dbContents}',
+ 'chown -R openldap:openldap /var/db/slapd.d /var/db/openldap',
+ '${specializations}/manualConfigDir/bin/switch-to-configuration test',
)
- '' + testScript;
- }) { inherit system pkgs; };
-}
+ machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"')
+ machine.succeed('ldapmodify -D cn=root,cn=config -w configpassword -f ${pkgs.writeText "rootpw.ldif" changeRootPw}')
+ machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w foobar -b "dc=example"')
+ '';
+})
diff --git a/pkgs/development/libraries/openldap/default.nix b/pkgs/development/libraries/openldap/default.nix
index 551a0827eee23..80c226a1058fc 100644
--- a/pkgs/development/libraries/openldap/default.nix
+++ b/pkgs/development/libraries/openldap/default.nix
@@ -93,18 +93,18 @@ stdenv.mkDerivation rec {
"ac_cv_func_memcmp_working=yes"
] ++ lib.optional stdenv.isFreeBSD "--with-pic";
- makeFlags = [
+ NIX_CFLAGS_COMPILE = [ "-DLDAPI_SOCK=\"/run/openldap/ldapi\"" ];
+
+ makeFlags= [
"CC=${stdenv.cc.targetPrefix}cc"
"STRIP=" # Disable install stripping as it breaks cross-compiling. We strip binaries anyway in fixupPhase.
+ "STRIP_OPTS="
"prefix=${placeholder "out"}"
"sysconfdir=${placeholder "out"}/etc"
"systemdsystemunitdir=${placeholder "out"}/lib/systemd/system"
# contrib modules require these
"moduledir=${placeholder "out"}/lib/modules"
"mandir=${placeholder "out"}/share/man"
- ] ++ lib.optionals (stdenv.buildPlatform != stdenv.hostPlatform) [
- # Can be unconditional, doing it like this to prevent a mass rebuild.
- "STRIP_OPTS="
];
extraContribModules = [