From 1fde3c35619bd445357077d816c72b0e589e0775 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sun, 2 Aug 2020 23:52:37 +0100 Subject: [PATCH 01/13] nixos/openldap: switch to slapd.d configuration The old slapd.conf is deprecated. Replace with slapd.d, and use this opportunity to write some structured settings. Incidentally, this fixes the fact that openldap is reported up before any checks have completed, by using forking mode. --- nixos/modules/misc/ids.nix | 4 +- nixos/modules/services/databases/openldap.nix | 371 ++++++++++++++---- nixos/tests/openldap.nix | 163 ++++++-- 3 files changed, 431 insertions(+), 107 deletions(-) diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index bafa2225040..cf0198d7b93 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -135,7 +135,7 @@ in #keys = 96; # unused #haproxy = 97; # dynamically allocated as of 2020-03-11 mongodb = 98; - openldap = 99; + #openldap = 99; # dynamically allocated as of PR#94610 #users = 100; # unused cgminer = 101; munin = 102; @@ -451,7 +451,7 @@ in keys = 96; #haproxy = 97; # dynamically allocated as of 2020-03-11 #mongodb = 98; # unused - openldap = 99; + #openldap = 99; # dynamically allocated as of PR#94610 munin = 102; #logcheck = 103; # unused #nix-ssh = 104; # unused diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index 7472538b887..afe24597e03 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -1,20 +1,19 @@ { config, lib, pkgs, ... }: with lib; - let - cfg = config.services.openldap; openldap = cfg.package; dataFile = pkgs.writeText "ldap-contents.ldif" cfg.declarativeContents; - configFile = pkgs.writeText "slapd.conf" ((optionalString cfg.defaultSchemas '' - include ${openldap.out}/etc/schema/core.schema - include ${openldap.out}/etc/schema/cosine.schema - include ${openldap.out}/etc/schema/inetorgperson.schema - include ${openldap.out}/etc/schema/nis.schema + configFile = pkgs.writeText "slapd.conf" ((optionalString (cfg.defaultSchemas != null && cfg.defaultSchemas) '' + include ${openldap}/etc/schema/core.schema + include ${openldap}/etc/schema/cosine.schema + include ${openldap}/etc/schema/inetorgperson.schema + include ${openldap}/etc/schema/nis.schema '') + '' - ${cfg.extraConfig} + pidfile /run/slapd/slapd.pid + ${if cfg.extraConfig != null then cfg.extraConfig else ""} database ${cfg.database} suffix ${cfg.suffix} rootdn ${cfg.rootdn} @@ -24,20 +23,79 @@ let include ${cfg.rootpwFile} ''} directory ${cfg.dataDir} - ${cfg.extraDatabaseConfig} + ${if cfg.extraDatabaseConfig != null then cfg.extraDatabaseConfig else ""} ''); - configOpts = if cfg.configDir == null then "-f ${configFile}" - else "-F ${cfg.configDir}"; -in -{ + configDir = if cfg.configDir != null then cfg.configDir else "/etc/openldap/slapd.d"; - ###### interface + ldapValueType = let + singleLdapValueType = types.either types.str (types.submodule { + options = { + path = mkOption { + type = types.path; + description = '' + A path containing the LDAP attribute. This is included at run-time, so + is recommended for storing secrets. + ''; + }; + }; + }); + in types.either singleLdapValueType (types.listOf singleLdapValueType); + ldapAttrsType = + let + options = { + attrs = mkOption { + type = types.attrsOf ldapValueType; + default = {}; + description = "Attributes of the parent entry."; + }; + children = mkOption { + # Hide the child attributes, to avoid infinite recursion in e.g. documentation + # Actual Nix evaluation is lazy, so this is not an issue there + type = let + hiddenOptions = lib.mapAttrs (name: attr: attr // { visible = false; }) options; + in types.attrsOf (types.submodule { options = hiddenOptions; }); + default = {}; + description = "Child entries of the current entry, with recursively the same structure."; + example = lib.literalExample '' + { + "cn=schema" = { + # The attribute used in the DN must be defined + attrs = { cn = "schema"; }; + children = { + # This entry's DN is expanded to "cn=foo,cn=schema" + "cn=foo" = { ... }; + }; + # These includes are inserted after "cn=schema", but before "cn=foo,cn=schema" + includes = [ ... ]; + }; + } + ''; + }; + includes = mkOption { + type = types.listOf types.path; + default = []; + description = '' + LDIF files to include after the parent's attributes but before its children. + ''; + }; + }; + in types.submodule { inherit options; }; + + valueToLdif = attr: values: let + singleValueToLdif = value: if lib.isAttrs value then "${attr}:< file://${value.path}" else "${attr}: ${value}"; + in if lib.isList values then map singleValueToLdif values else [ (singleValueToLdif values) ]; + + attrsToLdif = dn: { attrs, children, includes, ... }: ['' + dn: ${dn} + ${lib.concatStringsSep "\n" (lib.flatten (lib.mapAttrsToList valueToLdif attrs))} + ''] ++ (map (path: "include: file://${path}\n") includes) ++ ( + lib.flatten (lib.mapAttrsToList (name: value: attrsToLdif "${name},${dn}" value) children) + ); +in { options = { - services.openldap = { - enable = mkOption { type = types.bool; default = false; @@ -77,47 +135,91 @@ in example = [ "ldaps:///" ]; }; + settings = mkOption { + type = ldapAttrsType; + description = "Configuration for OpenLDAP, in OLC format"; + example = lib.literalExample '' + { + attrs.olcLogLevel = [ "stats" ]; + children = { + "cn=schema".includes = [ + "\${pkgs.openldap}/etc/schema/core.ldif" + "\${pkgs.openldap}/etc/schema/cosine.ldif" + "\${pkgs.openldap}/etc/schema/inetorgperson.ldif" + ]; + "olcDatabase={-1}frontend" = { + attrs = { + objectClass = "olcDatabaseConfig"; + olcDatabase = "{-1}frontend"; + olcAccess = [ "{0}to * by dn.exact=uidNumber=0+gidNumber=0,cn=peercred,cn=external,cn=auth manage stop by * none stop" ]; + }; + }; + "olcDatabase={0}config" = { + attrs = { + objectClass = "olcDatabaseConfig"; + olcDatabase = "{0}config"; + olcAccess = [ "{0}to * by * none break" ]; + }; + }; + "olcDatabase={1}mdb" = { + attrs = { + objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ]; + olcDatabase = "{1}mdb"; + olcDbDirectory = "/var/db/ldap"; + olcDbIndex = [ + "objectClass eq" + "cn pres,eq" + "uid pres,eq" + "sn pres,eq,subany" + ]; + olcSuffix = "dc=example,dc=com"; + olcAccess = [ "{0}to * by * read break" ]; + }; + }; + }; + }; + ''; + }; + + # These options are translated into settings dataDir = mkOption { - type = types.path; + type = types.nullOr types.path; default = "/var/db/openldap"; description = "The database directory."; }; defaultSchemas = mkOption { - type = types.bool; + type = types.nullOr types.bool; default = true; + description = '' Include the default schemas core, cosine, inetorgperson and nis. - This setting will be ignored if configDir is set. ''; }; database = mkOption { - type = types.str; + type = types.nullOr types.str; default = "mdb"; - description = '' - Database type to use for the LDAP. - This setting will be ignored if configDir is set. - ''; + description = "Backend to use for the first database."; }; suffix = mkOption { - type = types.str; + type = types.nullOr types.str; + default = null; example = "dc=example,dc=org"; description = '' - Specify the DN suffix of queries that will be passed to this backend - database. - This setting will be ignored if configDir is set. + Specify the DN suffix of queries that will be passed to the first + database database. ''; }; rootdn = mkOption { - type = types.str; + type = types.nullOr types.str; + default = null; example = "cn=admin,dc=example,dc=org"; description = '' Specify the distinguished name that is not subject to access control or administrative limit restrictions for operations on this database. - This setting will be ignored if configDir is set. ''; }; @@ -125,10 +227,9 @@ in type = types.nullOr types.str; default = null; description = '' - Password for the root user. - This setting will be ignored if configDir is set. - Using this option will store the root password in plain text in the - world-readable nix store. To avoid this the rootpwFile can be used. + Password for the root user.Using this option will store the root + password in plain text in the world-readable nix store. To avoid this + the rootpwFile can be used. ''; }; @@ -137,25 +238,36 @@ in default = null; description = '' Password file for the root user. - The file should contain the string rootpw followed by the password. - e.g.: rootpw mysecurepassword + + If the deprecated extraConfig or + extraDatabaseConfig options are set, this should + contain rootpw followed by the password + (e.g. rootpw thePasswordHere). + + Otherwise the file should contain only the password (no trailing + newline or leading rootpw). ''; }; logLevel = mkOption { - type = types.str; - default = "0"; - example = "acl trace"; - description = "The log level selector of slapd."; + type = types.nullOr (types.listOf types.str); + default = null; + example = literalExample "[ \"acl\" \"trace\" ]"; + description = "The log level."; }; + # This option overrides settings configDir = mkOption { type = types.nullOr types.path; default = null; - description = "Use this optional config directory instead of using slapd.conf"; + description = '' + Use this optional config directory instead of generating one from the + settings option. + ''; example = "/var/db/slapd.d"; }; + # These options are deprecated extraConfig = mkOption { type = types.lines; default = ""; @@ -164,10 +276,10 @@ in "; example = literalExample '' ''' - include ${openldap.out}/etc/schema/core.schema - include ${openldap.out}/etc/schema/cosine.schema - include ${openldap.out}/etc/schema/inetorgperson.schema - include ${openldap.out}/etc/schema/nis.schema + include ${openldap}/etc/schema/core.schema + include ${openldap}/etc/schema/cosine.schema + include ${openldap}/etc/schema/inetorgperson.schema + include ${openldap}/etc/schema/nis.schema database bdb suffix dc=example,dc=org @@ -244,57 +356,156 @@ in }; meta = { - maintainers = [ lib.maintainers.mic92 ]; + maintainers = with lib.maintainters; [ mic92 kwohlfahrt ]; }; - - ###### implementation - config = mkIf cfg.enable { - assertions = [ - { - assertion = cfg.configDir != null || cfg.rootpwFile != null || cfg.rootpw != null; - message = "services.openldap: Unless configDir is set, either rootpw or rootpwFile must be set"; - } - ]; + warnings = let + deprecations = [ + { old = "logLevel"; new = "attrs.olcLogLevel"; } + { old = "defaultSchemas"; + new = "children.\"cn=schema\".includes"; + newValue = "[\n ${lib.concatStringsSep "\n " [ + "\${pkgs.openldap}/etc/schema/core.ldif" + "\${pkgs.openldap}/etc/schema/cosine.ldif" + "\${pkgs.openldap}/etc/schema/inetorgperson.ldif" + "\${pkgs.openldap}/etc/schema/nis.ldif" + ]}\n ]"; } + { old = "database"; new = "children.\"cn={1}${cfg.database}\""; newValue = "{ }"; } + { old = "suffix"; new = "children.\"cn={1}${cfg.database}\".attrs.olcSuffix"; } + { old = "dataDir"; new = "children.\"cn={1}${cfg.database}\".attrs.olcDbDirectory"; } + { old = "rootdn"; new = "children.\"cn={1}${cfg.database}\".attrs.olcRootDN"; } + { old = "rootpw"; new = "children.\"cn={1}${cfg.database}\".attrs.olcRootPW"; } + { old = "rootpwFile"; + new = "children.\"cn={1}${cfg.database}\".attrs.olcRootPW"; + newValue = "{ path = \"${cfg.rootpwFile}\"; }"; + note = "The file should contain only the password (without \"rootpw \" as before)"; } + ]; + in (optional (cfg.extraConfig != "" || cfg.extraDatabaseConfig != "") '' + The options `extraConfig` and `extraDatabaseConfig` of `services.openldap` + are deprecated. This is due to the deprecation of `slapd.conf` + upstream. Please migrate to `services.openldap.settings`. + + After deploying this configuration, you can run: + slapcat -F ${configDir} -n0 -H 'ldap:///???(!(objectClass=olcSchemaConfig))' + on the same host to print your current configuration in LDIF format, + which should be straightforward to convert into Nix settings. + '') ++ (flatten (map (args@{old, new, ...}: lib.optional ((lib.hasAttr old cfg) && (lib.getAttr old cfg) != null) '' + The attribute `services.openldap.${old}` is deprecated. Please set it to + `null` and use the following option instead: + + services.openldap.settings.${new} = ${args.newValue or ( + let oldValue = (getAttr old cfg); + in if (isList oldValue) then "[ ${concatStringsSep " " oldValue} ]" else oldValue + )} + '') deprecations)) ++ (optional (cfg.configDir != null && (versionOlder config.system.stateVersion "20.09")) '' + The attribute `services.openldap.settings` now exists, and may be more + useful than `services.openldap.configDir`. If you continue to use + `configDir`, ensure that `olcPidFile` is set to "/run/slapd/slapd.pid". + + Set `system.stateVersion` to "20.09" or greater to silence this message. + ''); + + assertions = [{ + assertion = !(cfg.rootpwFile != null && cfg.rootpw != null); + message = "services.openldap: at most one of rootpw or rootpwFile must be set"; + }]; environment.systemPackages = [ openldap ]; + # Literal attributes must always be set (even if other top-level attributres are deprecated) + services.openldap.settings = { + attrs = { + objectClass = "olcGlobal"; + cn = "config"; + olcPidFile = "/run/slapd/slapd.pid"; + } // (lib.optionalAttrs (cfg.logLevel != null) { + olcLogLevel = cfg.logLevel; + }); + children = { + "cn=schema" = { + attrs = { + cn = "schema"; + objectClass = "olcSchemaConfig"; + }; + includes = lib.optionals (cfg.defaultSchemas != null && cfg.defaultSchemas) [ + "${openldap}/etc/schema/core.ldif" + "${openldap}/etc/schema/cosine.ldif" + "${openldap}/etc/schema/inetorgperson.ldif" + "${openldap}/etc/schema/nis.ldif" + ]; + }; + } // (lib.optionalAttrs (cfg.database != null) { + "olcDatabase={1}${cfg.database}".attrs = { + # objectClass is case-insensitive, so don't need to capitalize ${database} + objectClass = [ "olcdatabaseconfig" "olc${cfg.database}config" ]; + olcDatabase = "{1}${cfg.database}"; + } // (lib.optionalAttrs (cfg.suffix != null) { + olcSuffix = cfg.suffix; + }) // (lib.optionalAttrs (cfg.dataDir != null) { + olcDbDirectory = cfg.dataDir; + }) // (lib.optionalAttrs (cfg.rootdn != null) { + olcRootDN = cfg.rootdn; # TODO: Optional + }) // (lib.optionalAttrs (cfg.rootpw != null || cfg.rootpwFile != null) { + olcRootPW = (if cfg.rootpwFile != null then { path = cfg.rootpwFile; } else cfg.rootpw); # TODO: Optional + }); + }); + }; + systemd.services.openldap = { description = "LDAP server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; - preStart = '' + preStart = let + dbSettings = lib.filterAttrs (name: value: lib.hasPrefix "olcDatabase=" name) cfg.settings.children; + dataDirs = lib.mapAttrsToList (name: value: value.attrs.olcDbDirectory) dbSettings; + settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings)); + in '' mkdir -p /run/slapd chown -R "${cfg.user}:${cfg.group}" /run/slapd - ${optionalString (cfg.declarativeContents != null) '' - rm -Rf "${cfg.dataDir}" - ''} - mkdir -p "${cfg.dataDir}" - ${optionalString (cfg.declarativeContents != null) '' - ${openldap.out}/bin/slapadd ${configOpts} -l ${dataFile} - ''} - chown -R "${cfg.user}:${cfg.group}" "${cfg.dataDir}" - ${openldap}/bin/slaptest ${configOpts} + mkdir -p '${configDir}' ${lib.escapeShellArgs dataDirs} + chown "${cfg.user}:${cfg.group}" '${configDir}' ${lib.escapeShellArgs dataDirs} + + ${lib.optionalString (cfg.configDir == null) ( + if (cfg.extraConfig != "" || cfg.extraDatabaseConfig != "") then '' + rm -Rf '${configDir}'/* + # -u disables config generation, so just ignore the return code + ${openldap}/bin/slaptest -f ${configFile} -F ${configDir} || true + '' else '' + rm -Rf '${configDir}'/* + ${openldap}/bin/slapadd -F ${configDir} -n0 -l ${settingsFile} + '' + )} + chown -R "${cfg.user}:${cfg.group}" '${configDir}' + + ${optionalString (cfg.declarativeContents != null) '' + rm -Rf '${lib.head dataDirs}'/* + ${openldap}/bin/slapadd -F ${configDir} -n1 -l ${dataFile} + chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArgs dataDirs} + ''} + + ${openldap}/bin/slaptest -u -F ${configDir} ''; - serviceConfig.ExecStart = - "${openldap.out}/libexec/slapd -d '${cfg.logLevel}' " + - "-u '${cfg.user}' -g '${cfg.group}' " + - "-h '${concatStringsSep " " cfg.urlList}' " + - "${configOpts}"; + serviceConfig = { + ExecStart = lib.concatStringsSep " " [ + "${openldap}/libexec/slapd" + "-u '${cfg.user}'" + "-g '${cfg.group}'" + "-h '${concatStringsSep " " cfg.urlList}'" + "-F ${configDir}" + ]; + Type = "forking"; + PIDFile = cfg.settings.attrs.olcPidFile; + }; }; - users.users.openldap = - { name = cfg.user; - group = cfg.group; - uid = config.ids.uids.openldap; - }; - - users.groups.openldap = - { name = cfg.group; - gid = config.ids.gids.openldap; - }; + users.users = lib.optionalAttrs (cfg.user == "openldap") { + openldap = { group = cfg.group; }; + }; + users.groups = lib.optionalAttrs (cfg.group == "openldap") { + openldap = {}; + }; }; } diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index f8321a2c522..33b7b7f6608 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -1,33 +1,146 @@ -import ./make-test-python.nix { - name = "openldap"; - - machine = { pkgs, ... }: { - services.openldap = { - enable = true; - suffix = "dc=example"; - rootdn = "cn=root,dc=example"; - rootpw = "notapassword"; - database = "bdb"; - extraDatabaseConfig = '' - directory /var/db/openldap - ''; - declarativeContents = '' - dn: dc=example - objectClass: domain - dc: example - - dn: ou=users,dc=example - objectClass: organizationalUnit - ou: users - ''; - }; - }; +{ pkgs, system ? builtins.currentSystem, ... }: let + declarativeContents = '' + dn: dc=example + objectClass: domain + dc: example + dn: ou=users,dc=example + objectClass: organizationalUnit + ou: users + ''; testScript = '' machine.wait_for_unit("openldap.service") machine.succeed( - "systemctl status openldap.service", 'ldapsearch -LLL -D "cn=root,dc=example" -w notapassword -b "dc=example"', ) ''; +in { + # New-style configuration + current = import ./make-test-python.nix { + inherit testScript; + name = "openldap"; + + machine = { pkgs, ... }: { + services.openldap = { + inherit declarativeContents; + enable = true; + defaultSchemas = null; + dataDir = null; + database = null; + 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" = { + attrs = { + objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ]; + olcDatabase = "{1}mdb"; + olcDbDirectory = "/var/db/openldap"; + olcSuffix = "dc=example"; + olcRootDN = "cn=root,dc=example"; + olcRootPW = "notapassword"; + }; + }; + }; + }; + }; + }; + }; + + # Old-style configuration + shortOptions = import ./make-test-python.nix { + inherit testScript; + name = "openldap"; + + machine = { pkgs, ... }: { + services.openldap = { + inherit declarativeContents; + enable = true; + suffix = "dc=example"; + rootdn = "cn=root,dc=example"; + rootpw = "notapassword"; + }; + }; + }; + + # Manually managed configDir, for example if dynamic config is essential + manualConfigDir = import ./make-test-python.nix { + name = "openldap"; + + machine = { pkgs, ... }: { + services.openldap = { + enable = true; + configDir = "/var/db/slapd.d"; + # Silence warnings + defaultSchemas = null; + dataDir = null; + database = null; + }; + }; + + testScript = let + contents = pkgs.writeText "data.ldif" declarativeContents; + 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 + + 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 '' + 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", + ) + '' + testScript; + }; + + # extraConfig forces use of slapd.conf, test this until that option is removed + legacyConfig = import ./make-test-python.nix { + inherit testScript; + name = "openldap"; + + machine = { pkgs, ... }: { + services.openldap = { + inherit declarativeContents; + enable = true; + suffix = "dc=example"; + rootdn = "cn=root,dc=example"; + rootpw = "notapassword"; + extraConfig = '' + # No-op + ''; + extraDatabaseConfig = '' + # No-op + ''; + }; + }; + }; } From 057cb570beb9226b610dffa00cffd98b007a0686 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sun, 16 Aug 2020 20:17:06 +0100 Subject: [PATCH 02/13] nixos/openldap: Add delcarativeConfig by suffix Adding by index could be an issue if the user wanted the data to be added to a DB other than the first. --- nixos/modules/services/databases/openldap.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index afe24597e03..d03c8cd7cfd 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -474,14 +474,14 @@ in { ${openldap}/bin/slaptest -f ${configFile} -F ${configDir} || true '' else '' rm -Rf '${configDir}'/* - ${openldap}/bin/slapadd -F ${configDir} -n0 -l ${settingsFile} + ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} '' )} chown -R "${cfg.user}:${cfg.group}" '${configDir}' ${optionalString (cfg.declarativeContents != null) '' rm -Rf '${lib.head dataDirs}'/* - ${openldap}/bin/slapadd -F ${configDir} -n1 -l ${dataFile} + ${openldap}/bin/slapadd -F ${configDir} -b${cfg.suffix} -l ${dataFile} chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArgs dataDirs} ''} From 9528faf1829bbf77fa693e8700cf8b27eb0b869e Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Mon, 24 Aug 2020 00:07:24 +0100 Subject: [PATCH 03/13] nixos/openldap: Allow declarativeContents for multiple databases --- nixos/modules/services/databases/openldap.nix | 50 ++++++++++++------- nixos/tests/openldap.nix | 10 ++-- 2 files changed, 38 insertions(+), 22 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index d03c8cd7cfd..c3a8e03c880 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -5,7 +5,6 @@ let cfg = config.services.openldap; openldap = cfg.package; - dataFile = pkgs.writeText "ldap-contents.ldif" cfg.declarativeContents; configFile = pkgs.writeText "slapd.conf" ((optionalString (cfg.defaultSchemas != null && cfg.defaultSchemas) '' include ${openldap}/etc/schema/core.schema include ${openldap}/etc/schema/cosine.schema @@ -26,7 +25,7 @@ let ${if cfg.extraDatabaseConfig != null then cfg.extraDatabaseConfig else ""} ''); - configDir = if cfg.configDir != null then cfg.configDir else "/etc/openldap/slapd.d"; + configDir = lib.escapeShellArg (if cfg.configDir != null then cfg.configDir else "/etc/openldap/slapd.d"); ldapValueType = let singleLdapValueType = types.either types.str (types.submodule { @@ -209,7 +208,7 @@ in { example = "dc=example,dc=org"; description = '' Specify the DN suffix of queries that will be passed to the first - database database. + backend database. ''; }; @@ -292,10 +291,10 @@ in { }; declarativeContents = mkOption { - type = with types; nullOr lines; - default = null; + type = with types; either lines (attrsOf lines); + default = {}; description = '' - Declarative contents for the LDAP database, in LDIF format. + Declarative contents for the first LDAP database, in LDIF format. Note a few facts when using it. First, the database must be stored in the directory defined by @@ -359,6 +358,10 @@ in { maintainers = with lib.maintainters; [ mic92 kwohlfahrt ]; }; + # TODO: Check that dataDir/declarativeContents/configDir all match + # - deprecate declarativeContents = ''...''; + # - no declarativeContents = ''...'' if dataDir == null; + # - no declarativeContents = { ... } if configDir != null config = mkIf cfg.enable { warnings = let deprecations = [ @@ -458,32 +461,45 @@ in { after = [ "network.target" ]; preStart = let dbSettings = lib.filterAttrs (name: value: lib.hasPrefix "olcDatabase=" name) cfg.settings.children; - dataDirs = lib.mapAttrsToList (name: value: value.attrs.olcDbDirectory) dbSettings; + dataDirs = lib.mapAttrs' (name: value: lib.nameValuePair value.attrs.olcSuffix value.attrs.olcDbDirectory) + (lib.filterAttrs (_: value: value.attrs ? olcDbDirectory) dbSettings); settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings)); in '' mkdir -p /run/slapd chown -R "${cfg.user}:${cfg.group}" /run/slapd - mkdir -p '${configDir}' ${lib.escapeShellArgs dataDirs} - chown "${cfg.user}:${cfg.group}" '${configDir}' ${lib.escapeShellArgs dataDirs} + mkdir -p ${configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)} + chown "${cfg.user}:${cfg.group}" ${configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)} ${lib.optionalString (cfg.configDir == null) ( if (cfg.extraConfig != "" || cfg.extraDatabaseConfig != "") then '' - rm -Rf '${configDir}'/* + rm -Rf ${configDir}/* # -u disables config generation, so just ignore the return code ${openldap}/bin/slaptest -f ${configFile} -F ${configDir} || true '' else '' - rm -Rf '${configDir}'/* + rm -Rf ${configDir}/* ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} '' )} - chown -R "${cfg.user}:${cfg.group}" '${configDir}' + chown -R "${cfg.user}:${cfg.group}" ${configDir} - ${optionalString (cfg.declarativeContents != null) '' - rm -Rf '${lib.head dataDirs}'/* - ${openldap}/bin/slapadd -F ${configDir} -b${cfg.suffix} -l ${dataFile} - chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArgs dataDirs} - ''} + ${if types.lines.check cfg.declarativeContents then (let + dataFile = pkgs.writeText "ldap-contents.ldif" cfg.declarativeContents; + in '' + rm -rf ${lib.escapeShellArg cfg.dataDir}/* + ${openldap}/bin/slapadd -F ${configDir} -l ${dataFile} + chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg cfg.dataDir} + '') else (let + dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; + in '' + ${lib.concatStrings (lib.mapAttrsToList (dn: file: let + dataDir = lib.escapeShellArg (getAttr dn dataDirs); + in '' + rm -rf ${dataDir}/* + ${openldap}/bin/slapadd -F ${configDir} -b ${dn} -l ${file} + chown -R "${cfg.user}:${cfg.group}" ${dataDir} + '') dataFiles)} + '')} ${openldap}/bin/slaptest -u -F ${configDir} ''; diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index 33b7b7f6608..0c40073735e 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -1,5 +1,5 @@ { pkgs, system ? builtins.currentSystem, ... }: let - declarativeContents = '' + dbContents = '' dn: dc=example objectClass: domain dc: example @@ -22,7 +22,6 @@ in { machine = { pkgs, ... }: { services.openldap = { - inherit declarativeContents; enable = true; defaultSchemas = null; dataDir = null; @@ -49,6 +48,7 @@ in { }; }; }; + declarativeContents."dc=example" = dbContents; }; }; }; @@ -60,11 +60,11 @@ in { machine = { pkgs, ... }: { services.openldap = { - inherit declarativeContents; enable = true; suffix = "dc=example"; rootdn = "cn=root,dc=example"; rootpw = "notapassword"; + declarativeContents = dbContents; }; }; }; @@ -85,7 +85,7 @@ in { }; testScript = let - contents = pkgs.writeText "data.ldif" declarativeContents; + contents = pkgs.writeText "data.ldif" dbContents; config = pkgs.writeText "config.ldif" '' dn: cn=config cn: config @@ -129,7 +129,6 @@ in { machine = { pkgs, ... }: { services.openldap = { - inherit declarativeContents; enable = true; suffix = "dc=example"; rootdn = "cn=root,dc=example"; @@ -140,6 +139,7 @@ in { extraDatabaseConfig = '' # No-op ''; + declarativeContents = dbContents; }; }; }; From d05061c5cdd5662157591c75f45113c483443f4a Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Mon, 24 Aug 2020 00:19:35 +0100 Subject: [PATCH 04/13] nixos/openldap: Pick some PR nits --- nixos/modules/services/databases/openldap.nix | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index c3a8e03c880..0def1e8c34d 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -25,7 +25,7 @@ let ${if cfg.extraDatabaseConfig != null then cfg.extraDatabaseConfig else ""} ''); - configDir = lib.escapeShellArg (if cfg.configDir != null then cfg.configDir else "/etc/openldap/slapd.d"); + configDir = if cfg.configDir != null then cfg.configDir else "/etc/openldap/slapd.d"; ldapValueType = let singleLdapValueType = types.either types.str (types.submodule { @@ -249,7 +249,7 @@ in { }; logLevel = mkOption { - type = types.nullOr (types.listOf types.str); + type = types.nullOr (types.coercedTo types.str (lib.splitString " ") (types.listOf types.str)); default = null; example = literalExample "[ \"acl\" \"trace\" ]"; description = "The log level."; @@ -468,8 +468,8 @@ in { mkdir -p /run/slapd chown -R "${cfg.user}:${cfg.group}" /run/slapd - mkdir -p ${configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)} - chown "${cfg.user}:${cfg.group}" ${configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)} + 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) ( if (cfg.extraConfig != "" || cfg.extraDatabaseConfig != "") then '' @@ -481,13 +481,13 @@ in { ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} '' )} - chown -R "${cfg.user}:${cfg.group}" ${configDir} + chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir} ${if types.lines.check cfg.declarativeContents then (let dataFile = pkgs.writeText "ldap-contents.ldif" cfg.declarativeContents; in '' rm -rf ${lib.escapeShellArg cfg.dataDir}/* - ${openldap}/bin/slapadd -F ${configDir} -l ${dataFile} + ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -l ${dataFile} chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg cfg.dataDir} '') else (let dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; @@ -496,28 +496,28 @@ in { dataDir = lib.escapeShellArg (getAttr dn dataDirs); in '' rm -rf ${dataDir}/* - ${openldap}/bin/slapadd -F ${configDir} -b ${dn} -l ${file} + ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -b ${dn} -l ${file} chown -R "${cfg.user}:${cfg.group}" ${dataDir} '') dataFiles)} '')} - ${openldap}/bin/slaptest -u -F ${configDir} + ${openldap}/bin/slaptest -u -F ${lib.escapeShellArg configDir} ''; serviceConfig = { - ExecStart = lib.concatStringsSep " " [ - "${openldap}/libexec/slapd" - "-u '${cfg.user}'" - "-g '${cfg.group}'" - "-h '${concatStringsSep " " cfg.urlList}'" - "-F ${configDir}" - ]; + ExecStart = lib.escapeShellArgs ([ + "${openldap}/libexec/slapd" "-u" cfg.user "-g" cfg.group "-F" configDir + "-h" (lib.concatStringsSep " " cfg.urlList) + ]); Type = "forking"; PIDFile = cfg.settings.attrs.olcPidFile; }; }; users.users = lib.optionalAttrs (cfg.user == "openldap") { - openldap = { group = cfg.group; }; + openldap = { + group = cfg.group; + isSystemUser = true; + }; }; users.groups = lib.optionalAttrs (cfg.group == "openldap") { From adda7e62d0c2a80aa85e0c22ad806874812f9f54 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sun, 13 Sep 2020 22:42:14 +0100 Subject: [PATCH 05/13] nixos/openldap: Add support for base64 values --- nixos/modules/services/databases/openldap.nix | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index 0def1e8c34d..cb51a3cc575 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -28,17 +28,31 @@ let configDir = if cfg.configDir != null then cfg.configDir else "/etc/openldap/slapd.d"; ldapValueType = let - singleLdapValueType = types.either types.str (types.submodule { - options = { - path = mkOption { - type = types.path; - description = '' - A path containing the LDAP attribute. This is included at run-time, so - is recommended for storing secrets. - ''; + singleLdapValueType = types.oneOf [ + types.str + (types.submodule { + options = { + path = mkOption { + type = types.path; + description = '' + A path containing the LDAP attribute. This is included at run-time, so + is recommended for storing secrets. + ''; + }; }; - }; - }); + }) + (types.submodule { + options = { + base64 = mkOption { + type = types.str; + description = '' + A base64-encoded LDAP attribute. Useful for storing values which + contain special characters (e.g. newlines) in LDIF files. + ''; + }; + }; + }) + ]; in types.either singleLdapValueType (types.listOf singleLdapValueType); ldapAttrsType = @@ -83,8 +97,14 @@ let in types.submodule { inherit options; }; valueToLdif = attr: values: let - singleValueToLdif = value: if lib.isAttrs value then "${attr}:< file://${value.path}" else "${attr}: ${value}"; - in if lib.isList values then map singleValueToLdif values else [ (singleValueToLdif values) ]; + listValues = if lib.isList values then values else lib.singleton values; + in map (value: + if lib.isAttrs value then + if lib.hasAttr "path" value + then "${attr}:< file://${value.path}" + else "${attr}:: ${value.base64}" + else "${attr}: ${lib.replaceStrings [ "\n" ] [ "\n " ] value}" + ) listValues; attrsToLdif = dn: { attrs, children, includes, ... }: ['' dn: ${dn} From 5fafbee87a3ecce798df55bbaaabcead43fc17b6 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sun, 13 Sep 2020 22:42:54 +0100 Subject: [PATCH 06/13] nixos/openldap: Add release-notes for OLC config --- nixos/doc/manual/release-notes/rl-2103.xml | 8 ++++++++ nixos/modules/services/databases/openldap.nix | 8 +------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2103.xml b/nixos/doc/manual/release-notes/rl-2103.xml index 10d5cda7746..845aa841504 100644 --- a/nixos/doc/manual/release-notes/rl-2103.xml +++ b/nixos/doc/manual/release-notes/rl-2103.xml @@ -185,6 +185,14 @@ which is the new stable release. OpenAFS 1.6 was removed. + + + The openldap module now has support for OLC-style + configuration, users of the configDir option may wish + to migrate. If you continue to use configDir, ensure that + olcPidFile is set to /run/slapd/slapd.pid. + + diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index cb51a3cc575..f9355201602 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -421,13 +421,7 @@ in { let oldValue = (getAttr old cfg); in if (isList oldValue) then "[ ${concatStringsSep " " oldValue} ]" else oldValue )} - '') deprecations)) ++ (optional (cfg.configDir != null && (versionOlder config.system.stateVersion "20.09")) '' - The attribute `services.openldap.settings` now exists, and may be more - useful than `services.openldap.configDir`. If you continue to use - `configDir`, ensure that `olcPidFile` is set to "/run/slapd/slapd.pid". - - Set `system.stateVersion` to "20.09" or greater to silence this message. - ''); + '') deprecations)); assertions = [{ assertion = !(cfg.rootpwFile != null && cfg.rootpw != null); From 2050376caee44dd52e7aaa00a9bfac6b644e5bff Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sun, 13 Sep 2020 22:43:11 +0100 Subject: [PATCH 07/13] nixos/openldap: Mention schemas in migration hint --- nixos/modules/services/databases/openldap.nix | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index f9355201602..9de4c7fa41b 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -411,8 +411,10 @@ in { After deploying this configuration, you can run: slapcat -F ${configDir} -n0 -H 'ldap:///???(!(objectClass=olcSchemaConfig))' - on the same host to print your current configuration in LDIF format, - which should be straightforward to convert into Nix settings. + on the same host to print your current configuration in LDIF format, which + should be straightforward to convert into Nix settings. This does not show + your schema configuration (as this is unnecessarily verbose users of the + default schemas), so be sure to migrate that as well. '') ++ (flatten (map (args@{old, new, ...}: lib.optional ((lib.hasAttr old cfg) && (lib.getAttr old cfg) != null) '' The attribute `services.openldap.${old}` is deprecated. Please set it to `null` and use the following option instead: From 3f892c2174d3e215be2e12d6dacede3cd4db392c Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sun, 13 Sep 2020 23:20:23 +0100 Subject: [PATCH 08/13] nixos/openldap: Remove extraConfig options Instead of deprecating, as per PR feedback --- nixos/doc/manual/release-notes/rl-2103.xml | 18 +++ nixos/modules/services/databases/openldap.nix | 152 ++++-------------- nixos/tests/openldap.nix | 22 --- 3 files changed, 51 insertions(+), 141 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2103.xml b/nixos/doc/manual/release-notes/rl-2103.xml index 845aa841504..55c1229a164 100644 --- a/nixos/doc/manual/release-notes/rl-2103.xml +++ b/nixos/doc/manual/release-notes/rl-2103.xml @@ -192,6 +192,24 @@ to migrate. If you continue to use configDir, ensure that olcPidFile is set to /run/slapd/slapd.pid. + + As a result, extraConfig and extraDatabaseConfig + are removed. To help with migration, you can convert your slapd.conf + file to OLC configuration with the following script (find the location of this + configuration file by running systemctl status openldap, it is the + -f option. + + + TMPDIR=$(mktemp -d) + slaptest -f /path/to/slapd.conf $TMPDIR + slapcat -F $TMPDIR -n0 -H 'ldap:///???(!(objectClass=olcSchemaConfig))' + + + This will dump your current configuration in LDIF format, which should be + straightforward to convert into Nix settings. This does not show your schema + configuration, as this is unnecessarily verbose for users of the default schemas + and slaptest is buggy with schemas directly in the config file. + diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index 9de4c7fa41b..fb043df9d60 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -4,27 +4,6 @@ with lib; let cfg = config.services.openldap; openldap = cfg.package; - - configFile = pkgs.writeText "slapd.conf" ((optionalString (cfg.defaultSchemas != null && cfg.defaultSchemas) '' - include ${openldap}/etc/schema/core.schema - include ${openldap}/etc/schema/cosine.schema - include ${openldap}/etc/schema/inetorgperson.schema - include ${openldap}/etc/schema/nis.schema - '') + '' - pidfile /run/slapd/slapd.pid - ${if cfg.extraConfig != null then cfg.extraConfig else ""} - database ${cfg.database} - suffix ${cfg.suffix} - rootdn ${cfg.rootdn} - ${if (cfg.rootpw != null) then '' - rootpw ${cfg.rootpw} - '' else '' - include ${cfg.rootpwFile} - ''} - directory ${cfg.dataDir} - ${if cfg.extraDatabaseConfig != null then cfg.extraDatabaseConfig else ""} - ''); - configDir = if cfg.configDir != null then cfg.configDir else "/etc/openldap/slapd.d"; ldapValueType = let @@ -113,6 +92,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."; + in [ + (lib.mkRemovedOptionModule [ "services" "openldap" "extraConfig" ] deprecationNote) + (lib.mkRemovedOptionModule [ "services" "openldap" "extraDatabaseConfig" ] deprecationNote) + ]; options = { services.openldap = { enable = mkOption { @@ -280,36 +265,13 @@ in { type = types.nullOr types.path; default = null; description = '' - Use this optional config directory instead of generating one from the - settings option. + 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`. ''; example = "/var/db/slapd.d"; }; - # These options are deprecated - extraConfig = mkOption { - type = types.lines; - default = ""; - description = " - slapd.conf configuration - "; - example = literalExample '' - ''' - include ${openldap}/etc/schema/core.schema - include ${openldap}/etc/schema/cosine.schema - include ${openldap}/etc/schema/inetorgperson.schema - include ${openldap}/etc/schema/nis.schema - - database bdb - suffix dc=example,dc=org - rootdn cn=admin,dc=example,dc=org - # NOTE: change after first start - rootpw secret - directory /var/db/openldap - ''' - ''; - }; - declarativeContents = mkOption { type = with types; either lines (attrsOf lines); default = {}; @@ -337,41 +299,7 @@ in { # ... ''; }; - - extraDatabaseConfig = mkOption { - type = types.lines; - default = ""; - description = '' - slapd.conf configuration after the database option. - This setting will be ignored if configDir is set. - ''; - example = '' - # Indices to maintain for this directory - # unique id so equality match only - index uid eq - # allows general searching on commonname, givenname and email - index cn,gn,mail eq,sub - # allows multiple variants on surname searching - index sn eq,sub - # sub above includes subintial,subany,subfinal - # optimise department searches - index ou eq - # if searches will include objectClass uncomment following - # index objectClass eq - # shows use of default index parameter - index default eq,sub - # indices missing - uses default eq,sub - index telephonenumber - - # other database parameters - # read more in slapd.conf reference section - cachesize 10000 - checkpoint 128 15 - ''; - }; - }; - }; meta = { @@ -404,18 +332,7 @@ in { newValue = "{ path = \"${cfg.rootpwFile}\"; }"; note = "The file should contain only the password (without \"rootpw \" as before)"; } ]; - in (optional (cfg.extraConfig != "" || cfg.extraDatabaseConfig != "") '' - The options `extraConfig` and `extraDatabaseConfig` of `services.openldap` - are deprecated. This is due to the deprecation of `slapd.conf` - upstream. Please migrate to `services.openldap.settings`. - - After deploying this configuration, you can run: - slapcat -F ${configDir} -n0 -H 'ldap:///???(!(objectClass=olcSchemaConfig))' - on the same host to print your current configuration in LDIF format, which - should be straightforward to convert into Nix settings. This does not show - your schema configuration (as this is unnecessarily verbose users of the - default schemas), so be sure to migrate that as well. - '') ++ (flatten (map (args@{old, new, ...}: lib.optional ((lib.hasAttr old cfg) && (lib.getAttr old cfg) != null) '' + in (flatten (map (args@{old, new, ...}: lib.optional ((lib.hasAttr old cfg) && (lib.getAttr old cfg) != null) '' The attribute `services.openldap.${old}` is deprecated. Please set it to `null` and use the following option instead: @@ -487,35 +404,32 @@ in { 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) ( - if (cfg.extraConfig != "" || cfg.extraDatabaseConfig != "") then '' - rm -Rf ${configDir}/* - # -u disables config generation, so just ignore the return code - ${openldap}/bin/slaptest -f ${configFile} -F ${configDir} || true - '' else '' - rm -Rf ${configDir}/* - ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} - '' - )} + ${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} - ${if types.lines.check cfg.declarativeContents then (let - dataFile = pkgs.writeText "ldap-contents.ldif" cfg.declarativeContents; - in '' - rm -rf ${lib.escapeShellArg cfg.dataDir}/* - ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -l ${dataFile} - chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg cfg.dataDir} - '') else (let - dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; - in '' - ${lib.concatStrings (lib.mapAttrsToList (dn: file: let - dataDir = lib.escapeShellArg (getAttr dn dataDirs); + ${if types.lines.check cfg.declarativeContents + then (let + dataFile = pkgs.writeText "ldap-contents.ldif" cfg.declarativeContents; in '' - rm -rf ${dataDir}/* - ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -b ${dn} -l ${file} - chown -R "${cfg.user}:${cfg.group}" ${dataDir} - '') dataFiles)} - '')} + rm -rf ${lib.escapeShellArg cfg.dataDir}/* + ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -l ${dataFile} + chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg cfg.dataDir} + '') + else (let + dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; + in '' + ${lib.concatStrings (lib.mapAttrsToList (dn: file: let + dataDir = lib.escapeShellArg (getAttr dn dataDirs); + in '' + rm -rf ${dataDir}/* + ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -b ${dn} -l ${file} + chown -R "${cfg.user}:${cfg.group}" ${dataDir} + '') dataFiles)} + '') + } ${openldap}/bin/slaptest -u -F ${lib.escapeShellArg configDir} ''; diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index 0c40073735e..b6dd8f573d5 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -121,26 +121,4 @@ in { ) '' + testScript; }; - - # extraConfig forces use of slapd.conf, test this until that option is removed - legacyConfig = import ./make-test-python.nix { - inherit testScript; - name = "openldap"; - - machine = { pkgs, ... }: { - services.openldap = { - enable = true; - suffix = "dc=example"; - rootdn = "cn=root,dc=example"; - rootpw = "notapassword"; - extraConfig = '' - # No-op - ''; - extraDatabaseConfig = '' - # No-op - ''; - declarativeContents = dbContents; - }; - }; - }; } From b2ebffe18634e5038fe911b3bd2c74d26e7a7ab9 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sun, 27 Sep 2020 17:10:13 +0100 Subject: [PATCH 09/13] nixos/openldap: Fix indentation --- nixos/modules/services/databases/openldap.nix | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index fb043df9d60..6f1ac3ed717 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -51,26 +51,26 @@ let default = {}; description = "Child entries of the current entry, with recursively the same structure."; example = lib.literalExample '' - { - "cn=schema" = { - # The attribute used in the DN must be defined - attrs = { cn = "schema"; }; - children = { - # This entry's DN is expanded to "cn=foo,cn=schema" - "cn=foo" = { ... }; - }; - # These includes are inserted after "cn=schema", but before "cn=foo,cn=schema" - includes = [ ... ]; - }; - } - ''; + { + "cn=schema" = { + # The attribute used in the DN must be defined + attrs = { cn = "schema"; }; + children = { + # This entry's DN is expanded to "cn=foo,cn=schema" + "cn=foo" = { ... }; + }; + # These includes are inserted after "cn=schema", but before "cn=foo,cn=schema" + includes = [ ... ]; + }; + } + ''; }; includes = mkOption { type = types.listOf types.path; default = []; description = '' - LDIF files to include after the parent's attributes but before its children. - ''; + LDIF files to include after the parent's attributes but before its children. + ''; }; }; in types.submodule { inherit options; }; From ce1acd97a7162094accfb764cb1d33159dc5165f Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sun, 27 Sep 2020 18:03:40 +0100 Subject: [PATCH 10/13] nixos/openldap: fix path + base64 value types --- nixos/modules/services/databases/openldap.nix | 33 +++++-------------- nixos/tests/openldap.nix | 11 +++++-- 2 files changed, 17 insertions(+), 27 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index 6f1ac3ed717..c333f817d03 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -7,31 +7,14 @@ let configDir = if cfg.configDir != null then cfg.configDir else "/etc/openldap/slapd.d"; ldapValueType = let - singleLdapValueType = types.oneOf [ - types.str - (types.submodule { - options = { - path = mkOption { - type = types.path; - description = '' - A path containing the LDAP attribute. This is included at run-time, so - is recommended for storing secrets. - ''; - }; - }; - }) - (types.submodule { - options = { - base64 = mkOption { - type = types.str; - description = '' - A base64-encoded LDAP attribute. Useful for storing values which - contain special characters (e.g. newlines) in LDIF files. - ''; - }; - }; - }) - ]; + # Can't do types.either with multiple non-overlapping submodules, so define our own + singleLdapValueType = lib.mkOptionType rec { + name = "LDAP"; + description = "LDAP value"; + check = x: lib.isString x || (lib.isAttrs x && (x ? "path" || x ? "base64")); + merge = lib.mergeEqualOption; + }; + # We don't coerce to lists of single values, as some values must be unique in types.either singleLdapValueType (types.listOf singleLdapValueType); ldapAttrsType = diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index b6dd8f573d5..beaff916474 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -21,6 +21,7 @@ in { name = "openldap"; machine = { pkgs, ... }: { + environment.etc."openldap/root_password".text = "notapassword"; services.openldap = { enable = true; defaultSchemas = null; @@ -37,13 +38,19 @@ in { ]; }; "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"; - olcRootPW = "notapassword"; + olcRootDN = { + # cn=root,dc=example + base64 = "Y249cm9vdCxkYz1leGFtcGxl"; + }; + olcRootPW = { + path = "/etc/openldap/root_password"; + }; }; }; }; From fefc26f8449aba4d66a70f600932c9306f4db60b Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sun, 27 Sep 2020 21:50:25 +0100 Subject: [PATCH 11/13] nixos/openldap: use mkRenamedOptionModule This offers less helpful warnings, but makes the implementation considerably more straightforward. --- nixos/modules/services/databases/openldap.nix | 247 +++++------------- nixos/tests/openldap.nix | 15 +- 2 files changed, 73 insertions(+), 189 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index c333f817d03..aedf3873b04 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -11,7 +11,7 @@ let singleLdapValueType = lib.mkOptionType rec { name = "LDAP"; description = "LDAP value"; - check = x: lib.isString x || (lib.isAttrs x && (x ? "path" || x ? "base64")); + check = x: lib.isString x || (lib.isAttrs x && (x ? path || x ? base64)); merge = lib.mergeEqualOption; }; # We don't coerce to lists of single values, as some values must be unique @@ -80,6 +80,34 @@ in { 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: "${pkgs.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"; + }; + })) + (lib.mkRenamedOptionModule [ "services" "openldap" "rootpwFile" ] + [ "services" "openldap" "settings" "children" "olcDatabase={1}${cfg.database}" "attrs" "olcRootPW" "path"]) + (lib.mkRenamedOptionModule [ "services" "openldap" "suffix" ] + [ "services" "openldap" "settings" "children" "olcDatabase={1}${cfg.database}" "attrs" "olcSuffix"]) + (lib.mkRenamedOptionModule [ "services" "openldap" "dataDir" ] + [ "services" "openldap" "settings" "children" "olcDatabase={1}${cfg.database}" "attrs" "olcDbDirectory"]) + (lib.mkRenamedOptionModule [ "services" "openldap" "rootdn" ] + [ "services" "openldap" "settings" "children" "olcDatabase={1}${cfg.database}" "attrs" "olcRootDN"]) + (lib.mkRenamedOptionModule [ "services" "openldap" "rootpw" ] + [ "services" "openldap" "settings" "children" "olcDatabase={1}${cfg.database}" "attrs" "olcRootPW"]) ]; options = { services.openldap = { @@ -168,81 +196,6 @@ in { ''; }; - # These options are translated into settings - dataDir = mkOption { - type = types.nullOr types.path; - default = "/var/db/openldap"; - description = "The database directory."; - }; - - defaultSchemas = mkOption { - type = types.nullOr types.bool; - default = true; - - description = '' - Include the default schemas core, cosine, inetorgperson and nis. - ''; - }; - - database = mkOption { - type = types.nullOr types.str; - default = "mdb"; - description = "Backend to use for the first database."; - }; - - suffix = mkOption { - type = types.nullOr types.str; - default = null; - example = "dc=example,dc=org"; - description = '' - Specify the DN suffix of queries that will be passed to the first - backend database. - ''; - }; - - rootdn = mkOption { - type = types.nullOr types.str; - default = null; - example = "cn=admin,dc=example,dc=org"; - description = '' - Specify the distinguished name that is not subject to access control - or administrative limit restrictions for operations on this database. - ''; - }; - - rootpw = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Password for the root user.Using this option will store the root - password in plain text in the world-readable nix store. To avoid this - the rootpwFile can be used. - ''; - }; - - rootpwFile = mkOption { - type = types.nullOr types.str; - default = null; - description = '' - Password file for the root user. - - If the deprecated extraConfig or - extraDatabaseConfig options are set, this should - contain rootpw followed by the password - (e.g. rootpw thePasswordHere). - - Otherwise the file should contain only the password (no trailing - newline or leading rootpw). - ''; - }; - - logLevel = mkOption { - type = types.nullOr (types.coercedTo types.str (lib.splitString " ") (types.listOf types.str)); - default = null; - example = literalExample "[ \"acl\" \"trace\" ]"; - description = "The log level."; - }; - # This option overrides settings configDir = mkOption { type = types.nullOr types.path; @@ -256,119 +209,64 @@ in { }; declarativeContents = mkOption { - type = with types; either lines (attrsOf lines); + type = with types; attrsOf lines; default = {}; description = '' - Declarative contents for the first LDAP database, in LDIF format. + Declarative contents for the LDAP database, in LDIF format by suffix. - Note a few facts when using it. First, the database - must be stored in the directory defined by - dataDir. Second, all dataDir will be erased - when starting the LDAP server. Third, modifications to the database - are not prevented, they are just dropped on the next reboot of the - server. Finally, performance-wise the database and indexes are rebuilt - on each server startup, so this will slow down server startup, + All data will be erased when starting the LDAP server. Modifications + to the database are not prevented, they are just dropped on the next + 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. ''; - example = '' - dn: dc=example,dc=org - objectClass: domain - dc: example + example = lib.literalExample '' + { + "dc=example,dc=org" = ''' + dn= dn: dc=example,dc=org + objectClass: domain + dc: example - dn: ou=users,dc=example,dc=org - objectClass = organizationalUnit - ou: users + dn: ou=users,dc=example,dc=org + objectClass = organizationalUnit + ou: users - # ... + # ... + '''; + } ''; }; }; }; - meta = { - maintainers = with lib.maintainters; [ mic92 kwohlfahrt ]; - }; + meta.maintainers = with lib.maintainters; [ mic92 kwohlfahrt ]; - # TODO: Check that dataDir/declarativeContents/configDir all match - # - deprecate declarativeContents = ''...''; - # - no declarativeContents = ''...'' if dataDir == null; - # - no declarativeContents = { ... } if configDir != null config = mkIf cfg.enable { - warnings = let - deprecations = [ - { old = "logLevel"; new = "attrs.olcLogLevel"; } - { old = "defaultSchemas"; - new = "children.\"cn=schema\".includes"; - newValue = "[\n ${lib.concatStringsSep "\n " [ - "\${pkgs.openldap}/etc/schema/core.ldif" - "\${pkgs.openldap}/etc/schema/cosine.ldif" - "\${pkgs.openldap}/etc/schema/inetorgperson.ldif" - "\${pkgs.openldap}/etc/schema/nis.ldif" - ]}\n ]"; } - { old = "database"; new = "children.\"cn={1}${cfg.database}\""; newValue = "{ }"; } - { old = "suffix"; new = "children.\"cn={1}${cfg.database}\".attrs.olcSuffix"; } - { old = "dataDir"; new = "children.\"cn={1}${cfg.database}\".attrs.olcDbDirectory"; } - { old = "rootdn"; new = "children.\"cn={1}${cfg.database}\".attrs.olcRootDN"; } - { old = "rootpw"; new = "children.\"cn={1}${cfg.database}\".attrs.olcRootPW"; } - { old = "rootpwFile"; - new = "children.\"cn={1}${cfg.database}\".attrs.olcRootPW"; - newValue = "{ path = \"${cfg.rootpwFile}\"; }"; - note = "The file should contain only the password (without \"rootpw \" as before)"; } - ]; - in (flatten (map (args@{old, new, ...}: lib.optional ((lib.hasAttr old cfg) && (lib.getAttr old cfg) != null) '' - The attribute `services.openldap.${old}` is deprecated. Please set it to - `null` and use the following option instead: - - services.openldap.settings.${new} = ${args.newValue or ( - let oldValue = (getAttr old cfg); - in if (isList oldValue) then "[ ${concatStringsSep " " oldValue} ]" else oldValue - )} - '') deprecations)); - assertions = [{ - assertion = !(cfg.rootpwFile != null && cfg.rootpw != null); - message = "services.openldap: at most one of rootpw or rootpwFile must be set"; + assertion = lib.length (lib.attrNames cfg.settings.children) >= 2 || cfg ? database; + message = '' + No OpenLDAP database is defined. Configure one with `services.openldap.settings` + or `services.openldap.database` (legacy). + ''; }]; environment.systemPackages = [ openldap ]; - # Literal attributes must always be set (even if other top-level attributres are deprecated) + # Literal attributes must always be set services.openldap.settings = { attrs = { objectClass = "olcGlobal"; cn = "config"; olcPidFile = "/run/slapd/slapd.pid"; - } // (lib.optionalAttrs (cfg.logLevel != null) { - olcLogLevel = cfg.logLevel; - }); + }; children = { "cn=schema" = { attrs = { cn = "schema"; objectClass = "olcSchemaConfig"; }; - includes = lib.optionals (cfg.defaultSchemas != null && cfg.defaultSchemas) [ - "${openldap}/etc/schema/core.ldif" - "${openldap}/etc/schema/cosine.ldif" - "${openldap}/etc/schema/inetorgperson.ldif" - "${openldap}/etc/schema/nis.ldif" - ]; }; - } // (lib.optionalAttrs (cfg.database != null) { - "olcDatabase={1}${cfg.database}".attrs = { - # objectClass is case-insensitive, so don't need to capitalize ${database} - objectClass = [ "olcdatabaseconfig" "olc${cfg.database}config" ]; - olcDatabase = "{1}${cfg.database}"; - } // (lib.optionalAttrs (cfg.suffix != null) { - olcSuffix = cfg.suffix; - }) // (lib.optionalAttrs (cfg.dataDir != null) { - olcDbDirectory = cfg.dataDir; - }) // (lib.optionalAttrs (cfg.rootdn != null) { - olcRootDN = cfg.rootdn; # TODO: Optional - }) // (lib.optionalAttrs (cfg.rootpw != null || cfg.rootpwFile != null) { - olcRootPW = (if cfg.rootpwFile != null then { path = cfg.rootpwFile; } else cfg.rootpw); # TODO: Optional - }); - }); + }; }; systemd.services.openldap = { @@ -376,10 +274,19 @@ in { wantedBy = [ "multi-user.target" ]; after = [ "network.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); - 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 (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 @@ -393,27 +300,7 @@ in { '')} chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir} - ${if types.lines.check cfg.declarativeContents - then (let - dataFile = pkgs.writeText "ldap-contents.ldif" cfg.declarativeContents; - in '' - rm -rf ${lib.escapeShellArg cfg.dataDir}/* - ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -l ${dataFile} - chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg cfg.dataDir} - '') - else (let - dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; - in '' - ${lib.concatStrings (lib.mapAttrsToList (dn: file: let - dataDir = lib.escapeShellArg (getAttr dn dataDirs); - in '' - rm -rf ${dataDir}/* - ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -b ${dn} -l ${file} - chown -R "${cfg.user}:${cfg.group}" ${dataDir} - '') dataFiles)} - '') - } - + ${lib.concatStrings (map mkLoadScript (lib.attrNames cfg.declarativeContents))} ${openldap}/bin/slaptest -u -F ${lib.escapeShellArg configDir} ''; serviceConfig = { diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index beaff916474..ac1e1a7596f 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -24,9 +24,6 @@ in { environment.etc."openldap/root_password".text = "notapassword"; services.openldap = { enable = true; - defaultSchemas = null; - dataDir = null; - database = null; settings = { children = { "cn=schema" = { @@ -61,17 +58,21 @@ in { }; # Old-style configuration - shortOptions = import ./make-test-python.nix { + oldOptions = import ./make-test-python.nix { inherit testScript; name = "openldap"; machine = { pkgs, ... }: { services.openldap = { enable = true; + logLevel = "stats acl"; + defaultSchemas = true; + database = "mdb"; suffix = "dc=example"; rootdn = "cn=root,dc=example"; rootpw = "notapassword"; - declarativeContents = dbContents; + dataDir = "/var/db/openldap"; + declarativeContents."dc=example" = dbContents; }; }; }; @@ -84,10 +85,6 @@ in { services.openldap = { enable = true; configDir = "/var/db/slapd.d"; - # Silence warnings - defaultSchemas = null; - dataDir = null; - database = null; }; }; From db5bb4e26bb1cdc8a79dd639c2585722fdf2ce18 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sun, 27 Sep 2020 23:19:01 +0100 Subject: [PATCH 12/13] nixos/openldap: Fix sssd-ldap test Use this as a test of the migration warnings/functionality. --- nixos/modules/services/databases/openldap.nix | 49 ++++++++--------- nixos/tests/openldap.nix | 1 - nixos/tests/sssd-ldap.nix | 52 ++++++++++--------- 3 files changed, 50 insertions(+), 52 deletions(-) diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index aedf3873b04..94a5c573768 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -3,6 +3,7 @@ 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"; @@ -77,6 +78,12 @@ let 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) @@ -85,7 +92,7 @@ in { (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: "${pkgs.openldap}/etc/schema/${schema}.ldif") [ "core" "cosine" "inetorgperson" "nis" ]))) + map (schema: "${openldap}/etc/schema/${schema}.ldif") [ "core" "cosine" "inetorgperson" "nis" ]))) (lib.mkChangedOptionModule [ "services" "openldap" "database" ] [ "services" "openldap" "settings" "children" ] (config: let @@ -97,17 +104,15 @@ in { 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" ] + ); })) - (lib.mkRenamedOptionModule [ "services" "openldap" "rootpwFile" ] - [ "services" "openldap" "settings" "children" "olcDatabase={1}${cfg.database}" "attrs" "olcRootPW" "path"]) - (lib.mkRenamedOptionModule [ "services" "openldap" "suffix" ] - [ "services" "openldap" "settings" "children" "olcDatabase={1}${cfg.database}" "attrs" "olcSuffix"]) - (lib.mkRenamedOptionModule [ "services" "openldap" "dataDir" ] - [ "services" "openldap" "settings" "children" "olcDatabase={1}${cfg.database}" "attrs" "olcDbDirectory"]) - (lib.mkRenamedOptionModule [ "services" "openldap" "rootdn" ] - [ "services" "openldap" "settings" "children" "olcDatabase={1}${cfg.database}" "attrs" "olcRootDN"]) - (lib.mkRenamedOptionModule [ "services" "openldap" "rootpw" ] - [ "services" "openldap" "settings" "children" "olcDatabase={1}${cfg.database}" "attrs" "olcRootPW"]) + (mkDatabaseOption "rootpwFile" [ "olcRootPW" "path" ]) + (mkDatabaseOption "suffix" [ "olcSuffix" ]) + (mkDatabaseOption "dataDir" [ "olcDbDirectory" ]) + (mkDatabaseOption "rootdn" [ "olcRootDN" ]) + (mkDatabaseOption "rootpw" [ "olcRootPW" ]) ]; options = { services.openldap = { @@ -242,14 +247,10 @@ in { meta.maintainers = with lib.maintainters; [ mic92 kwohlfahrt ]; config = mkIf cfg.enable { - assertions = [{ - assertion = lib.length (lib.attrNames cfg.settings.children) >= 2 || cfg ? database; - message = '' - No OpenLDAP database is defined. Configure one with `services.openldap.settings` - or `services.openldap.database` (legacy). - ''; - }]; - + 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; environment.systemPackages = [ openldap ]; # Literal attributes must always be set @@ -259,13 +260,9 @@ in { cn = "config"; olcPidFile = "/run/slapd/slapd.pid"; }; - children = { - "cn=schema" = { - attrs = { - cn = "schema"; - objectClass = "olcSchemaConfig"; - }; - }; + children."cn=schema".attrs = { + cn = "schema"; + objectClass = "olcSchemaConfig"; }; }; diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index ac1e1a7596f..e9339523ca9 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -71,7 +71,6 @@ in { suffix = "dc=example"; rootdn = "cn=root,dc=example"; rootpw = "notapassword"; - dataDir = "/var/db/openldap"; declarativeContents."dc=example" = dbContents; }; }; diff --git a/nixos/tests/sssd-ldap.nix b/nixos/tests/sssd-ldap.nix index b68403a0102..8cb398d0e17 100644 --- a/nixos/tests/sssd-ldap.nix +++ b/nixos/tests/sssd-ldap.nix @@ -1,4 +1,4 @@ -import ./make-test-python.nix ({ pkgs, ... }: +({ pkgs, ... }: let dbDomain = "example.org"; dbSuffix = "dc=example,dc=org"; @@ -7,8 +7,7 @@ import ./make-test-python.nix ({ pkgs, ... }: ldapRootPassword = "foobar"; testUser = "alice"; - in - { + in import ./make-test-python.nix { name = "sssd-ldap"; meta = with pkgs.stdenv.lib.maintainers; { @@ -18,34 +17,37 @@ import ./make-test-python.nix ({ pkgs, ... }: machine = { pkgs, ... }: { services.openldap = { enable = true; + database = "mdb"; rootdn = "cn=${ldapRootUser},${dbSuffix}"; rootpw = ldapRootPassword; suffix = dbSuffix; - declarativeContents = '' - dn: ${dbSuffix} - objectClass: top - objectClass: dcObject - objectClass: organization - o: ${dbDomain} + declarativeContents = { + ${dbSuffix} = '' + dn: ${dbSuffix} + objectClass: top + objectClass: dcObject + objectClass: organization + o: ${dbDomain} - dn: ou=posix,${dbSuffix} - objectClass: top - objectClass: organizationalUnit + dn: ou=posix,${dbSuffix} + objectClass: top + objectClass: organizationalUnit - dn: ou=accounts,ou=posix,${dbSuffix} - objectClass: top - objectClass: organizationalUnit + dn: ou=accounts,ou=posix,${dbSuffix} + objectClass: top + objectClass: organizationalUnit - dn: uid=${testUser},ou=accounts,ou=posix,${dbSuffix} - objectClass: person - objectClass: posixAccount - # userPassword: somePasswordHash - homeDirectory: /home/${testUser} - uidNumber: 1234 - gidNumber: 1234 - cn: "" - sn: "" - ''; + dn: uid=${testUser},ou=accounts,ou=posix,${dbSuffix} + objectClass: person + objectClass: posixAccount + # userPassword: somePasswordHash + homeDirectory: /home/${testUser} + uidNumber: 1234 + gidNumber: 1234 + cn: "" + sn: "" + ''; + }; }; services.sssd = { From c96f18feee69f1bd621ba4ddeb180e95d4278f27 Mon Sep 17 00:00:00 2001 From: Kai Wohlfahrt Date: Sun, 27 Sep 2020 23:23:31 +0100 Subject: [PATCH 13/13] nixos/openldap: migrate sssd-ldap to new settings --- nixos/tests/openldap.nix | 14 ++++++-------- nixos/tests/sssd-ldap.nix | 24 ++++++++++++++++++++---- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index e9339523ca9..392fae24346 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -26,14 +26,12 @@ in { 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" - ]; - }; + "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 = { diff --git a/nixos/tests/sssd-ldap.nix b/nixos/tests/sssd-ldap.nix index 8cb398d0e17..4831eaa4ba2 100644 --- a/nixos/tests/sssd-ldap.nix +++ b/nixos/tests/sssd-ldap.nix @@ -17,10 +17,26 @@ machine = { pkgs, ... }: { services.openldap = { enable = true; - database = "mdb"; - rootdn = "cn=${ldapRootUser},${dbSuffix}"; - rootpw = ldapRootPassword; - suffix = dbSuffix; + 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" = { + attrs = { + objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ]; + olcDatabase = "{1}mdb"; + olcDbDirectory = "/var/db/openldap"; + olcSuffix = dbSuffix; + olcRootDN = "cn=${ldapRootUser},${dbSuffix}"; + olcRootPW = ldapRootPassword; + }; + }; + }; + }; declarativeContents = { ${dbSuffix} = '' dn: ${dbSuffix}