From 4a15f979c3e7bad79655f3f7c01036f6b6d93b96 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sat, 27 Nov 2021 21:42:38 +0000 Subject: [PATCH 01/13] Remove old assertions --- nixos/modules/services/databases/openldap.nix | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index c7880393f3d00..b4b4010742b28 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -220,10 +220,7 @@ 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 ++ map (dn: { + assertions = map (dn: { assertion = dataDirs ? "${dn}"; message = '' declarative DB ${dn} does not exist in "servies.openldap.settings" or it exists but the "olcDbDirectory" From 2de5d457cbfbc0847a55f0cee0a63499563a1d0d Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Mon, 29 Nov 2021 00:06:03 +0000 Subject: [PATCH 02/13] Working /etc config This is messy, because it means the config construction script has to have root access to create the config dir and set permissions. Additionally, the intention with systemd seems to be that ConfigurationDirectory is world-readable, which is not suitable for us because it contains secrets. Look into LoadCredential for alternatives. --- nixos/modules/services/databases/openldap.nix | 56 +++++++++++-------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index b4b4010742b28..f8546ea927d93 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -220,13 +220,19 @@ in { meta.maintainers = with lib.maintainers; [ mic92 kwohlfahrt ]; config = mkIf cfg.enable { - assertions = map (dn: { + assertions = (map (dn: { assertion = dataDirs ? "${dn}"; message = '' - declarative DB ${dn} does not exist in "servies.openldap.settings" or it exists but the "olcDbDirectory" + declarative DB ${dn} does not exist in "services.openldap.settings" or it exists but the "olcDbDirectory" is not prefixed by "/var/lib/openldap/" ''; - }) declarativeDNs; + }) declarativeDNs) ++ (map (dir: { + # FIXME: Decide if config is going to live in /etc or /var + assertion = !(hasPrefix "slapd.d" dir); + message = '' + database path may not be "/var/lib/openldap/slapd.d", this path is used for configuration. + ''; + }) (attrValues dataDirs)); environment.systemPackages = [ openldap ]; # Literal attributes must always be set @@ -246,35 +252,41 @@ in { description = "LDAP server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; - preStart = let - settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings)); - dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; - mkLoadScript = dn: let - dataDir = lib.escapeShellArg ("/var/lib/openldap/" + getAttr dn dataDirs); - in '' - rm -rf ${dataDir}/* - ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -b ${dn} -l ${getAttr dn dataFiles} + serviceConfig = let + # This cannot be built in a derivation, because it needs filesystem access for file + writeConfig = let + settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings)); + in pkgs.writeShellScript "openldap-pre" '' + ${openldap}/bin/slapadd -F /etc/openldap -bcn=config -l ${settingsFile} + chgrp -R ${cfg.user} /etc/openldap + chmod -R g+r /etc/openldap/cn=config.ldif /etc/openldap/cn=config ''; - in '' - ${lib.optionalString useDefaultConfDir '' - rm -rf ${configDir}/* - ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} - ''} - - ${lib.concatStrings (map mkLoadScript declarativeDNs)} - ${openldap}/bin/slaptest -u -F ${lib.escapeShellArg configDir} - ''; - serviceConfig = { + writeContents = let + dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; + mkLoadScript = dn: '' + rm -rf /var/lib/openldap/${lib.escapeShellArg (getAttr dn dataDirs)}/* + ${openldap}/bin/slapadd -F /etc/openldap -b ${dn} -l ${getAttr dn dataFiles} + ''; + in pkgs.writeShellScript "openldap-pre" '' + ${lib.concatStrings (map mkLoadScript declarativeDNs)} + ${openldap}/bin/slaptest -u -F /etc/openldap + ''; + in { User = cfg.user; Group = cfg.group; Type = "forking"; + ExecStartPre = [ + "+${writeConfig}" + "${writeContents}" + ]; ExecStart = lib.escapeShellArgs ([ - "${openldap}/libexec/slapd" "-F" configDir + "${openldap}/libexec/slapd" "-F" "/etc/openldap" "-h" (lib.concatStringsSep " " cfg.urlList) ]); StateDirectory = [ "openldap/slapd.d" ] ++ additionalStateDirectories; StateDirectoryMode = "700"; RuntimeDirectory = "openldap"; + ConfigurationDirectory = "openldap"; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; PIDFile = cfg.settings.attrs.olcPidFile; From 0213325deb25e4cdd051140498f8a121acff8ea9 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Mon, 29 Nov 2021 21:01:53 +0000 Subject: [PATCH 03/13] Fix manual config dir case --- nixos/modules/services/databases/openldap.nix | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index f8546ea927d93..5fda4b753a030 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -6,7 +6,7 @@ let legacyOptions = [ "rootpwFile" "suffix" "dataDir" "rootdn" "rootpw" ]; openldap = cfg.package; useDefaultConfDir = cfg.configDir == null; - configDir = if cfg.configDir != null then cfg.configDir else "/var/lib/openldap/slapd.d"; + configDir = if cfg.configDir != null then cfg.configDir else "$CONFIGURATION_DIRECTORY"; dbSettings = filterAttrs (name: value: hasPrefix "olcDatabase=" name) cfg.settings.children; dataDirs = mapAttrs' (_: value: nameValuePair value.attrs.olcSuffix (removePrefix "/var/lib/openldap/" value.attrs.olcDbDirectory)) @@ -256,31 +256,30 @@ in { # This cannot be built in a derivation, because it needs filesystem access for file writeConfig = let settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings)); - in pkgs.writeShellScript "openldap-pre" '' - ${openldap}/bin/slapadd -F /etc/openldap -bcn=config -l ${settingsFile} - chgrp -R ${cfg.user} /etc/openldap - chmod -R g+r /etc/openldap/cn=config.ldif /etc/openldap/cn=config + in pkgs.writeShellScript "openldap-config" '' + ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} + chgrp -R ${cfg.group} ${configDir} + chmod -R g+r ${configDir} ''; writeContents = let dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; mkLoadScript = dn: '' rm -rf /var/lib/openldap/${lib.escapeShellArg (getAttr dn dataDirs)}/* - ${openldap}/bin/slapadd -F /etc/openldap -b ${dn} -l ${getAttr dn dataFiles} + ${openldap}/bin/slapadd -F ${configDir} -b ${dn} -l ${getAttr dn dataFiles} ''; - in pkgs.writeShellScript "openldap-pre" '' + in pkgs.writeShellScript "openldap-data" '' ${lib.concatStrings (map mkLoadScript declarativeDNs)} - ${openldap}/bin/slaptest -u -F /etc/openldap + ${openldap}/bin/slaptest -u -F ${configDir} ''; in { User = cfg.user; Group = cfg.group; Type = "forking"; - ExecStartPre = [ - "+${writeConfig}" + ExecStartPre = (lib.optional (cfg.configDir == null) "+${writeConfig}") ++ [ "${writeContents}" ]; ExecStart = lib.escapeShellArgs ([ - "${openldap}/libexec/slapd" "-F" "/etc/openldap" + "${openldap}/libexec/slapd" "-F" configDir "-h" (lib.concatStringsSep " " cfg.urlList) ]); StateDirectory = [ "openldap/slapd.d" ] ++ additionalStateDirectories; From 94442de9607ad75a9caa2f793a158ab76eadd431 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Mon, 29 Nov 2021 22:01:39 +0000 Subject: [PATCH 04/13] Migrate openldap tests to subtests --- nixos/tests/openldap.nix | 146 ++++++++++++++++++--------------------- 1 file changed, 66 insertions(+), 80 deletions(-) diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index b077838b4de56..1204fa1992f90 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -1,7 +1,4 @@ -{ pkgs ? (import ../.. { inherit system; config = { }; }) -, system ? builtins.currentSystem -, ... -}: +import ./make-test-python.nix ({ pkgs, ... }: let dbContents = '' @@ -13,99 +10,88 @@ let objectClass: organizationalUnit ou: users ''; - testScript = '' + testDbExists = '' machine.wait_for_unit("openldap.service") machine.succeed( 'ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"', ) ''; + manualConfig = pkgs.writeText "config.ldif" '' + dn: cn=config + cn: config + objectClass: olcGlobal + olcLogLevel: stats + olcPidFile: /run/openldap/slapd.pid + + 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={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"; - 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/lib/openldap/current"; - olcSuffix = "dc=example"; - olcRootDN = { - # cn=root,dc=example - base64 = "Y249cm9vdCxkYz1leGFtcGxl"; - }; - olcRootPW = { - path = "/etc/openldap/root_password"; - }; + 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/lib/openldap/current"; + olcSuffix = "dc=example"; + olcRootDN = { + # cn=root,dc=example + base64 = "Y249cm9vdCxkYz1leGFtcGxl"; + }; + olcRootPW = { + path = "/etc/openldap/root_password"; }; }; }; }; - declarativeContents."dc=example" = dbContents; }; + declarativeContents."dc=example" = dbContents; }; - }) { inherit pkgs system; }; - - # Manually managed configDir, for example if dynamic config is essential - manualConfigDir = import ./make-test-python.nix ({ pkgs, ... }: { - name = "openldap"; - - machine = { pkgs, ... }: { - services.openldap = { - enable = true; - configDir = "/var/db/slapd.d"; - }; + specialisation.manualConfigDir.configuration = { ... }: { + services.openldap.configDir = "/var/db/slapd.d"; }; + }; - testScript = let - contents = pkgs.writeText "data.ldif" dbContents; - config = pkgs.writeText "config.ldif" '' - dn: cn=config - cn: config - objectClass: olcGlobal - olcLogLevel: stats - olcPidFile: /run/openldap/slapd.pid - - dn: cn=schema,cn=config - cn: schema - objectClass: olcSchemaConfig + testScript = { nodes, ... }: let config = nodes.machine.config.system.build.toplevel; in '' + ${testDbExists} - 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={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("handles 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}", + "slapadd -F /var/db/slapd.d -n0 -l ${manualConfig}", + "slapadd -F /var/db/slapd.d -n1 -l ${pkgs.writeText "data.ldif" dbContents}", "chown -R openldap:openldap /var/db/slapd.d /var/db/openldap", - "systemctl restart openldap", + "${config}/specialisation/manualConfigDir/bin/switch-to-configuration test", ) - '' + testScript; - }) { inherit system pkgs; }; -} + ${testDbExists} + ''; +}) From ab82a22744351cd400a8d321f86f947b308795c8 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Tue, 30 Nov 2021 00:26:53 +0000 Subject: [PATCH 05/13] Test mutable/immutable config TODO: add mutable/immutable content, for symmetry. --- nixos/modules/services/databases/openldap.nix | 28 ++++++-- nixos/tests/openldap.nix | 65 ++++++++++++++----- 2 files changed, 69 insertions(+), 24 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index 5fda4b753a030..a29ccf3799b96 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -3,10 +3,14 @@ with lib; let cfg = config.services.openldap; - legacyOptions = [ "rootpwFile" "suffix" "dataDir" "rootdn" "rootpw" ]; openldap = cfg.package; - useDefaultConfDir = cfg.configDir == null; - configDir = if cfg.configDir != null then cfg.configDir else "$CONFIGURATION_DIRECTORY"; + escapeSystemd = s: replaceStrings ["%"] ["%%"] s; + + legacyOptions = [ "rootpwFile" "suffix" "dataDir" "rootdn" "rootpw" ]; + configDir = + if cfg.configDir != null then cfg.configDir + else if cfg.mutableConfig then "/var/lib/openldap/slapd.d" + else "$CONFIGURATION_DIRECTORY"; dbSettings = filterAttrs (name: value: hasPrefix "olcDatabase=" name) cfg.settings.children; dataDirs = mapAttrs' (_: value: nameValuePair value.attrs.olcSuffix (removePrefix "/var/lib/openldap/" value.attrs.olcDbDirectory)) @@ -182,6 +186,16 @@ in { 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. + ''; + }; + declarativeContents = mkOption { type = with types; attrsOf lines; default = {}; @@ -259,7 +273,7 @@ in { in pkgs.writeShellScript "openldap-config" '' ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} chgrp -R ${cfg.group} ${configDir} - chmod -R g+r ${configDir} + chmod -R ${if cfg.mutableConfig then "g+rw" else "g+r"} ${configDir} ''; writeContents = let dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; @@ -278,10 +292,10 @@ in { ExecStartPre = (lib.optional (cfg.configDir == null) "+${writeConfig}") ++ [ "${writeContents}" ]; - ExecStart = lib.escapeShellArgs ([ + ExecStart = lib.escapeShellArgs [ "${openldap}/libexec/slapd" "-F" configDir - "-h" (lib.concatStringsSep " " cfg.urlList) - ]); + "-h" (escapeSystemd (lib.concatStringsSep " " cfg.urlList)) + ]; StateDirectory = [ "openldap/slapd.d" ] ++ additionalStateDirectories; StateDirectoryMode = "700"; RuntimeDirectory = "openldap"; diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index 1204fa1992f90..d53b2d277ef14 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -10,12 +10,6 @@ let objectClass: organizationalUnit ou: users ''; - testDbExists = '' - machine.wait_for_unit("openldap.service") - machine.succeed( - 'ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"', - ) - ''; manualConfig = pkgs.writeText "config.ldif" '' dn: cn=config cn: config @@ -31,6 +25,11 @@ let 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 + olcAccess: {0}to * by * manage stop + dn: olcDatabase={1}mdb,cn=config objectClass: olcDatabaseConfig objectClass: olcMdbConfig @@ -48,15 +47,24 @@ in { environment.etc."openldap/root_password".text = "notapassword"; services.openldap = { enable = true; + urlList = [ "ldap:///" "ldapi://%2Frun%2Fopenldap%2Fldapi" ]; 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" = { + "${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"; + # FIXME: Clean up this, in case somebody tries to copy it + olcAccess = "{0}to * by * manage stop"; + }; + }; + "olcDatabase={1}mdb" = { # This tests string, base64 and path values, as well as lists of string values attrs = { objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ]; @@ -76,13 +84,34 @@ in { }; declarativeContents."dc=example" = dbContents; }; - specialisation.manualConfigDir.configuration = { ... }: { - services.openldap.configDir = "/var/db/slapd.d"; + specialisation = { + mutableConfig.configuration = { ... }: { + services.openldap.mutableConfig = true; + }; + manualConfigDir.configuration = { ... }: { + services.openldap.configDir = "/var/db/slapd.d"; + }; }; }; - testScript = { nodes, ... }: let config = nodes.machine.config.system.build.toplevel; in '' - ${testDbExists} + testScript = { nodes, ... }: let + config = nodes.machine.config.system.build.toplevel; + changeRootPW = pkgs.writeText "changeRootPW.ldif" '' + dn: olcDatabase={1}mdb,cn=config + changetype: modify + replace: olcRootPW + olcRootPW: foobar + ''; + in '' + machine.wait_for_unit("openldap.service") + machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"') + machine.fail("ldapmodify -Y EXTERNAL -H ldapi://%2Frun%2Fopenldap%2Fldapi -f ${changeRootPW}") + + with subtest("handles mutable config"): + machine.succeed("${config}/specialisation/mutableConfig/bin/switch-to-configuration test") + machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"') + machine.succeed('ldapmodify -Y EXTERNAL -H ldapi://%2Frun%2Fopenldap%2Fldapi -f ${changeRootPW}') + machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w foobar -b "dc=example"') with subtest("handles manual config dir"): machine.succeed( @@ -92,6 +121,8 @@ in { "chown -R openldap:openldap /var/db/slapd.d /var/db/openldap", "${config}/specialisation/manualConfigDir/bin/switch-to-configuration test", ) - ${testDbExists} + machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"') + machine.succeed('ldapmodify -Y EXTERNAL -H ldapi://%2Frun%2Fopenldap%2Fldapi -f ${changeRootPW}') + machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w foobar -b "dc=example"') ''; }) From e537a497270438ad8a98ba334d7f5f7ac0fe2b14 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Tue, 30 Nov 2021 23:43:43 +0000 Subject: [PATCH 06/13] Move declarative config back into /var --- nixos/modules/services/databases/openldap.nix | 33 ++++++++++--------- nixos/tests/openldap.nix | 4 +-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index a29ccf3799b96..1e4dfc6c0e460 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -5,12 +5,7 @@ let cfg = config.services.openldap; openldap = cfg.package; escapeSystemd = s: replaceStrings ["%"] ["%%"] s; - - legacyOptions = [ "rootpwFile" "suffix" "dataDir" "rootdn" "rootpw" ]; - configDir = - if cfg.configDir != null then cfg.configDir - else if cfg.mutableConfig then "/var/lib/openldap/slapd.d" - else "$CONFIGURATION_DIRECTORY"; + configDir = if cfg.configDir != null then cfg.configDir else "/var/lib/openldap/slapd.d"; dbSettings = filterAttrs (name: value: hasPrefix "olcDatabase=" name) cfg.settings.children; dataDirs = mapAttrs' (_: value: nameValuePair value.attrs.olcSuffix (removePrefix "/var/lib/openldap/" value.attrs.olcDbDirectory)) @@ -22,7 +17,13 @@ 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 might be worth defining a { creds = ...; } option, leveraging + # systemd's LoadCredentials for secrets. That should remove the last + # barrier to using DynamicUser for openldap. + 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; }; @@ -241,7 +242,6 @@ in { is not prefixed by "/var/lib/openldap/" ''; }) declarativeDNs) ++ (map (dir: { - # FIXME: Decide if config is going to live in /etc or /var assertion = !(hasPrefix "slapd.d" dir); message = '' database path may not be "/var/lib/openldap/slapd.d", this path is used for configuration. @@ -267,13 +267,15 @@ in { wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; serviceConfig = let - # This cannot be built in a derivation, because it needs filesystem access for file + # This cannot be built in a derivation, because it needs filesystem access for included files writeConfig = let settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings)); in pkgs.writeShellScript "openldap-config" '' - ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} - chgrp -R ${cfg.group} ${configDir} - chmod -R ${if cfg.mutableConfig then "g+rw" else "g+r"} ${configDir} + ${lib.optionalString (!cfg.mutableConfig) "rm -rf ${configDir}/*"} + if [ -z "$(ls -A ${configDir})" ]; then + ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} + fi + chmod -R ${if cfg.mutableConfig then "u+rw" else "u+r-w"} ${configDir} ''; writeContents = let dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; @@ -289,9 +291,9 @@ in { User = cfg.user; Group = cfg.group; Type = "forking"; - ExecStartPre = (lib.optional (cfg.configDir == null) "+${writeConfig}") ++ [ - "${writeContents}" - ]; + ExecStartPre = + (lib.optional (cfg.configDir == null) "${writeConfig}") + ++ [ "${writeContents}" ]; ExecStart = lib.escapeShellArgs [ "${openldap}/libexec/slapd" "-F" configDir "-h" (escapeSystemd (lib.concatStringsSep " " cfg.urlList)) @@ -299,7 +301,6 @@ in { StateDirectory = [ "openldap/slapd.d" ] ++ additionalStateDirectories; StateDirectoryMode = "700"; RuntimeDirectory = "openldap"; - ConfigurationDirectory = "openldap"; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; PIDFile = cfg.settings.attrs.olcPidFile; diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index d53b2d277ef14..76effa2264f4e 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -60,8 +60,7 @@ in { attrs = { objectClass = "olcDatabaseConfig"; olcDatabase = "{0}config"; - # FIXME: Clean up this, in case somebody tries to copy it - olcAccess = "{0}to * by * manage stop"; + olcAccess = "{0}to * by dn.exact=uidNumber=0+gidNumber=0,cn=peercred,cn=external,cn=auth manage by * +0 stop"; }; }; "olcDatabase={1}mdb" = { @@ -111,6 +110,7 @@ in { machine.succeed("${config}/specialisation/mutableConfig/bin/switch-to-configuration test") machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"') machine.succeed('ldapmodify -Y EXTERNAL -H ldapi://%2Frun%2Fopenldap%2Fldapi -f ${changeRootPW}') + machine.systemctl('restart openldap') machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w foobar -b "dc=example"') with subtest("handles manual config dir"): From e242eef97659d83f90727c74e089625814365a36 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Wed, 1 Dec 2021 00:05:08 +0000 Subject: [PATCH 07/13] Use network socket for config --- nixos/modules/services/databases/openldap.nix | 1 + nixos/tests/openldap.nix | 20 +++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index 1e4dfc6c0e460..f1a2388693627 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -300,6 +300,7 @@ in { ]; StateDirectory = [ "openldap/slapd.d" ] ++ additionalStateDirectories; StateDirectoryMode = "700"; + # TODO: Patch openldap to put the ldapi:/// socket here RuntimeDirectory = "openldap"; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index 76effa2264f4e..07b6d637d5711 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -28,7 +28,8 @@ let dn: olcDatabase={0}config,cn=config olcDatabase: {0}config objectClass: olcDatabaseConfig - olcAccess: {0}to * by * manage stop + olcRootDN: cn=root,cn=config + olcRootPW: configpassword dn: olcDatabase={1}mdb,cn=config objectClass: olcDatabaseConfig @@ -47,7 +48,7 @@ in { environment.etc."openldap/root_password".text = "notapassword"; services.openldap = { enable = true; - urlList = [ "ldap:///" "ldapi://%2Frun%2Fopenldap%2Fldapi" ]; + urlList = [ "ldap:///" ]; settings = { children = { "cn=schema".includes = [ @@ -60,7 +61,8 @@ in { attrs = { objectClass = "olcDatabaseConfig"; olcDatabase = "{0}config"; - olcAccess = "{0}to * by dn.exact=uidNumber=0+gidNumber=0,cn=peercred,cn=external,cn=auth manage by * +0 stop"; + olcRootDN = "cn=root,cn=config"; + olcRootPW = "configpassword"; }; }; "olcDatabase={1}mdb" = { @@ -90,6 +92,9 @@ in { manualConfigDir.configuration = { ... }: { services.openldap.configDir = "/var/db/slapd.d"; }; + localSocket.configuration = { ... }: { + services.openldap.urlList = [ "ldapi:///" ]; + }; }; }; @@ -104,15 +109,18 @@ in { in '' machine.wait_for_unit("openldap.service") machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"') - machine.fail("ldapmodify -Y EXTERNAL -H ldapi://%2Frun%2Fopenldap%2Fldapi -f ${changeRootPW}") + machine.fail("ldapmodify -D cn=root,cn=config -w configpassword -f ${changeRootPW}") with subtest("handles mutable config"): machine.succeed("${config}/specialisation/mutableConfig/bin/switch-to-configuration test") machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"') - machine.succeed('ldapmodify -Y EXTERNAL -H ldapi://%2Frun%2Fopenldap%2Fldapi -f ${changeRootPW}') + machine.succeed("ldapmodify -D cn=root,cn=config -w configpassword -f ${changeRootPW}") machine.systemctl('restart openldap') machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w foobar -b "dc=example"') + #with subtest("local IPC socket works"): + # machine.succeed("${config}/specialisation/localSocket/bin/switch-to-configuration test") + with subtest("handles manual config dir"): machine.succeed( "mkdir -p /var/db/slapd.d /var/db/openldap", @@ -122,7 +130,7 @@ in { "${config}/specialisation/manualConfigDir/bin/switch-to-configuration test", ) machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"') - machine.succeed('ldapmodify -Y EXTERNAL -H ldapi://%2Frun%2Fopenldap%2Fldapi -f ${changeRootPW}') + machine.succeed("ldapmodify -D cn=root,cn=config -w configpassword -f ${changeRootPW}") machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w foobar -b "dc=example"') ''; }) From 4b701b8cab6d6c4c91e26ca92a6d520fb4155999 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Wed, 1 Dec 2021 00:19:32 +0000 Subject: [PATCH 08/13] Patch openldap local socket --- nixos/tests/openldap.nix | 8 +------- .../libraries/openldap/default-socket-path.patch | 13 +++++++++++++ pkgs/development/libraries/openldap/default.nix | 2 ++ 3 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 pkgs/development/libraries/openldap/default-socket-path.patch diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index 07b6d637d5711..a961da4dfcdad 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -48,7 +48,7 @@ in { environment.etc."openldap/root_password".text = "notapassword"; services.openldap = { enable = true; - urlList = [ "ldap:///" ]; + urlList = [ "ldap:///" "ldapi:///" ]; settings = { children = { "cn=schema".includes = [ @@ -92,9 +92,6 @@ in { manualConfigDir.configuration = { ... }: { services.openldap.configDir = "/var/db/slapd.d"; }; - localSocket.configuration = { ... }: { - services.openldap.urlList = [ "ldapi:///" ]; - }; }; }; @@ -118,9 +115,6 @@ in { machine.systemctl('restart openldap') machine.succeed('ldapsearch -LLL -D "cn=root,dc=example" -w foobar -b "dc=example"') - #with subtest("local IPC socket works"): - # machine.succeed("${config}/specialisation/localSocket/bin/switch-to-configuration test") - with subtest("handles manual config dir"): machine.succeed( "mkdir -p /var/db/slapd.d /var/db/openldap", diff --git a/pkgs/development/libraries/openldap/default-socket-path.patch b/pkgs/development/libraries/openldap/default-socket-path.patch new file mode 100644 index 0000000000000..0ae3bae35d529 --- /dev/null +++ b/pkgs/development/libraries/openldap/default-socket-path.patch @@ -0,0 +1,13 @@ +diff --git a/include/ldap_defaults.h b/include/ldap_defaults.h +index 916d6bc66..14e08724a 100644 +--- a/include/ldap_defaults.h ++++ b/include/ldap_defaults.h +@@ -39,7 +39,7 @@ + #define LDAP_ENV_PREFIX "LDAP" + + /* default ldapi:// socket */ +-#define LDAPI_SOCK LDAP_RUNDIR LDAP_DIRSEP "run" LDAP_DIRSEP "ldapi" ++#define LDAPI_SOCK LDAP_RUNDIR LDAP_DIRSEP "run" LDAP_DIRSEP "openldap" LDAP_DIRSEP "ldapi" + + /* + * SLAPD DEFINITIONS diff --git a/pkgs/development/libraries/openldap/default.nix b/pkgs/development/libraries/openldap/default.nix index f9e2b3c0b3fc7..be1e2e5a2bd84 100644 --- a/pkgs/development/libraries/openldap/default.nix +++ b/pkgs/development/libraries/openldap/default.nix @@ -12,6 +12,8 @@ stdenv.mkDerivation rec { sha256 = "sha256-V7WSVL4V0L9qmrPVFMHAV3ewISMpFTMTSofJRGj49Hs="; }; + patches = [ ./default-socket-path.patch ]; + # TODO: separate "out" and "bin" outputs = [ "out" "dev" "man" "devdoc" ]; From ef953cac59fd614569d870c8952e454d97b41a44 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Wed, 1 Dec 2021 23:25:44 +0000 Subject: [PATCH 09/13] Add note about LoadCredentials --- nixos/modules/services/databases/openldap.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index f1a2388693627..9602ecd40f101 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -17,9 +17,10 @@ let # Can't do types.either with multiple non-overlapping submodules, so define our own singleLdapValueType = lib.mkOptionType rec { name = "LDAP"; - # TODO: It might be worth defining a { creds = ...; } option, leveraging + # TODO: It might be worth defining a { secret = ...; } option, leveraging # systemd's LoadCredentials for secrets. That should remove the last - # barrier to using DynamicUser for openldap. + # barrier to using DynamicUser for openldap. However, this is blocked on + # $CREDENTIALS_DIRECTORY being available in ExecStartPre. description = '' LDAP value - either a string, or an attrset containing `path` or `base64`, for included values or base-64 encoded values respectively. @@ -300,7 +301,6 @@ in { ]; StateDirectory = [ "openldap/slapd.d" ] ++ additionalStateDirectories; StateDirectoryMode = "700"; - # TODO: Patch openldap to put the ldapi:/// socket here RuntimeDirectory = "openldap"; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; From 717da75b48530cee4b33cd66c848cf8aefffd31a Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Thu, 2 Dec 2021 00:25:58 +0000 Subject: [PATCH 10/13] Properly fail in shell scripts --- nixos/modules/services/databases/openldap.nix | 12 +++++++++--- nixos/tests/openldap.nix | 8 ++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index 9602ecd40f101..1886acf3b19af 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -272,6 +272,8 @@ in { writeConfig = let settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings)); in pkgs.writeShellScript "openldap-config" '' + set -euo pipefail + ${lib.optionalString (!cfg.mutableConfig) "rm -rf ${configDir}/*"} if [ -z "$(ls -A ${configDir})" ]; then ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} @@ -285,8 +287,9 @@ in { ${openldap}/bin/slapadd -F ${configDir} -b ${dn} -l ${getAttr dn dataFiles} ''; in pkgs.writeShellScript "openldap-data" '' - ${lib.concatStrings (map mkLoadScript declarativeDNs)} - ${openldap}/bin/slaptest -u -F ${configDir} + set -euo pipefail + + ${lib.concatStrings (map mkLoadScript declarativeDNs)} ''; in { User = cfg.user; @@ -294,7 +297,10 @@ in { Type = "forking"; ExecStartPre = (lib.optional (cfg.configDir == null) "${writeConfig}") - ++ [ "${writeContents}" ]; + ++ [ + "${writeContents}" + "${openldap}/bin/slaptest -u -F ${configDir}" + ]; ExecStart = lib.escapeShellArgs [ "${openldap}/libexec/slapd" "-F" configDir "-h" (escapeSystemd (lib.concatStringsSep " " cfg.urlList)) diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index a961da4dfcdad..aa017ebf21a71 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -89,8 +89,12 @@ in { mutableConfig.configuration = { ... }: { services.openldap.mutableConfig = true; }; - manualConfigDir.configuration = { ... }: { - services.openldap.configDir = "/var/db/slapd.d"; + manualConfigDir = { + inheritParentConfig = false; + configuration = { ... }: { + services.openldap.enable = true; + services.openldap.configDir = "/var/db/slapd.d"; + }; }; }; }; From 3b703647f50e8ae6bc84939f6f2c455fef6f9809 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Thu, 2 Dec 2021 00:34:33 +0000 Subject: [PATCH 11/13] Add assertion for declarative DB + configDir --- nixos/modules/services/databases/openldap.nix | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index 1886acf3b19af..d5677e34fd700 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -236,16 +236,21 @@ in { meta.maintainers = with lib.maintainers; [ mic92 kwohlfahrt ]; config = mkIf cfg.enable { - assertions = (map (dn: { + assertions = [{ + assertion = (cfg.configDir != null) -> declarativeDNs == []; + message = '' + Declarative DB contents (${dn}) are not supported with user-managed configuration directory". + ''; + }] ++ (map (dn: { assertion = dataDirs ? "${dn}"; message = '' - declarative DB ${dn} does not exist in "services.openldap.settings" or it exists but the "olcDbDirectory" + Declarative DB ${dn} does not exist in "services.openldap.settings" or it exists but the "olcDbDirectory" is not prefixed by "/var/lib/openldap/" ''; }) declarativeDNs) ++ (map (dir: { assertion = !(hasPrefix "slapd.d" dir); message = '' - database path may not be "/var/lib/openldap/slapd.d", this path is used for configuration. + Database path may not be "/var/lib/openldap/slapd.d", this path is used for configuration. ''; }) (attrValues dataDirs)); environment.systemPackages = [ openldap ]; From b8e8d7406e57108eef6f5cbae587be420fafd9c9 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Thu, 2 Dec 2021 00:43:38 +0000 Subject: [PATCH 12/13] Add each DB separately --- nixos/modules/services/databases/openldap.nix | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index d5677e34fd700..8df15e353903f 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -10,9 +10,11 @@ let dbSettings = filterAttrs (name: value: hasPrefix "olcDatabase=" name) cfg.settings.children; dataDirs = mapAttrs' (_: value: nameValuePair value.attrs.olcSuffix (removePrefix "/var/lib/openldap/" value.attrs.olcDbDirectory)) (lib.filterAttrs (_: value: value.attrs ? olcDbDirectory && hasPrefix "/var/lib/openldap/" value.attrs.olcDbDirectory) dbSettings); - declarativeDNs = attrNames cfg.declarativeContents; additionalStateDirectories = map (sfx: "openldap/" + sfx) (attrValues dataDirs); + dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; + declarativeDNs = attrNames cfg.declarativeContents; + ldapValueType = let # Can't do types.either with multiple non-overlapping submodules, so define our own singleLdapValueType = lib.mkOptionType rec { @@ -285,27 +287,20 @@ in { fi chmod -R ${if cfg.mutableConfig then "u+rw" else "u+r-w"} ${configDir} ''; - writeContents = let - dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; - mkLoadScript = dn: '' - rm -rf /var/lib/openldap/${lib.escapeShellArg (getAttr dn dataDirs)}/* - ${openldap}/bin/slapadd -F ${configDir} -b ${dn} -l ${getAttr dn dataFiles} - ''; - in pkgs.writeShellScript "openldap-data" '' - set -euo pipefail + writeContents = pkgs.writeShellScript "openldap-load" '' + set -euo pipefail - ${lib.concatStrings (map mkLoadScript declarativeDNs)} + rm -rf /var/lib/openldap/$2/* + ${openldap}/bin/slapadd -F ${configDir} -b $1 -l $3 ''; in { User = cfg.user; Group = cfg.group; Type = "forking"; ExecStartPre = - (lib.optional (cfg.configDir == null) "${writeConfig}") - ++ [ - "${writeContents}" - "${openldap}/bin/slaptest -u -F ${configDir}" - ]; + (lib.optional (cfg.configDir == null) writeConfig) + ++ (map (dn: lib.escapeShellArgs [writeContents dn (getAttr dn dataDirs) (getAttr dn dataFiles)]) declarativeDNs) + ++ [ "${openldap}/bin/slaptest -u -F ${configDir}" ]; ExecStart = lib.escapeShellArgs [ "${openldap}/libexec/slapd" "-F" configDir "-h" (escapeSystemd (lib.concatStringsSep " " cfg.urlList)) From 824720b7701506de0e510e98c8e9a930668fc107 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sat, 4 Dec 2021 10:46:07 +0000 Subject: [PATCH 13/13] Don't set statedir if using manual config --- nixos/modules/services/databases/openldap.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index 8df15e353903f..1b4a7f31e9770 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -305,7 +305,7 @@ in { "${openldap}/libexec/slapd" "-F" configDir "-h" (escapeSystemd (lib.concatStringsSep " " cfg.urlList)) ]; - StateDirectory = [ "openldap/slapd.d" ] ++ additionalStateDirectories; + StateDirectory = lib.optional (cfg.configDir == null) ([ "openldap/slapd.d" ] ++ additionalStateDirectories); StateDirectoryMode = "700"; RuntimeDirectory = "openldap"; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ];