diff options
author | Martin Weinelt <mweinelt@users.noreply.github.com> | 2022-07-22 00:26:32 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-22 00:26:32 +0200 |
commit | 457d109dcd4ce568bbe17bf7a6f741ebaccd1334 (patch) | |
tree | 1df44386d49f1307f43e75cb67c1b7443ffb7cc5 | |
parent | 97a9f89dfed84f8dcc8e9e17ae99ae6a2aa7b852 (diff) | |
parent | 60d1c1d9ad3717150fc38bd2b1b974b511cd17b4 (diff) | |
download | nixpkgs-457d109dcd4ce568bbe17bf7a6f741ebaccd1334.tar nixpkgs-457d109dcd4ce568bbe17bf7a6f741ebaccd1334.tar.gz nixpkgs-457d109dcd4ce568bbe17bf7a6f741ebaccd1334.tar.bz2 nixpkgs-457d109dcd4ce568bbe17bf7a6f741ebaccd1334.tar.lz nixpkgs-457d109dcd4ce568bbe17bf7a6f741ebaccd1334.tar.xz nixpkgs-457d109dcd4ce568bbe17bf7a6f741ebaccd1334.tar.zst nixpkgs-457d109dcd4ce568bbe17bf7a6f741ebaccd1334.zip |
Merge pull request #179597 from Mic92/openldap-path
[staging] openldap: remove deprecated options, improve encapsulation
-rw-r--r-- | nixos/modules/services/databases/openldap.nix | 183 | ||||
-rw-r--r-- | nixos/tests/openldap.nix | 219 | ||||
-rw-r--r-- | pkgs/development/libraries/openldap/default.nix | 8 |
3 files changed, 217 insertions, 193 deletions
diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index d80d1b07b97..5006a3065e9 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 + <literal>path</literal> or <literal>base64</literal> 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 - <literal>settings</literal> option. Overrides all NixOS settings. If - you use this option,ensure `olcPidFile` is set to `/run/slapd/slapd.conf`. + <literal>settings</literal> 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 + <literal>true</literal>, 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 + <code>services.openldap.settings</code> and the + <code>olcDbDirectory</code> must begin with + <literal>"/var/lib/openldap"</literal>. ''; 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 3c388119d5d..04e2650e380 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 551a0827eee..80c226a1058 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 = [ |