nixos/openldap: use mkRenamedOptionModule

This offers less helpful warnings, but makes the implementation
considerably more straightforward.
This commit is contained in:
Kai Wohlfahrt 2020-09-27 21:50:25 +01:00
parent ce1acd97a7
commit fefc26f844
2 changed files with 73 additions and 189 deletions

View File

@ -11,7 +11,7 @@ let
singleLdapValueType = lib.mkOptionType rec { singleLdapValueType = lib.mkOptionType rec {
name = "LDAP"; name = "LDAP";
description = "LDAP value"; 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; merge = lib.mergeEqualOption;
}; };
# We don't coerce to lists of single values, as some values must be unique # We don't coerce to lists of single values, as some values must be unique
@ -80,6 +80,34 @@ in {
in [ in [
(lib.mkRemovedOptionModule [ "services" "openldap" "extraConfig" ] deprecationNote) (lib.mkRemovedOptionModule [ "services" "openldap" "extraConfig" ] deprecationNote)
(lib.mkRemovedOptionModule [ "services" "openldap" "extraDatabaseConfig" ] 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 = { options = {
services.openldap = { 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 <literal>rootpwFile</literal> can be used.
'';
};
rootpwFile = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
Password file for the root user.
If the deprecated <literal>extraConfig</literal> or
<literal>extraDatabaseConfig</literal> options are set, this should
contain <literal>rootpw</literal> followed by the password
(e.g. <literal>rootpw thePasswordHere</literal>).
Otherwise the file should contain only the password (no trailing
newline or leading <literal>rootpw</literal>).
'';
};
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 # This option overrides settings
configDir = mkOption { configDir = mkOption {
type = types.nullOr types.path; type = types.nullOr types.path;
@ -256,22 +209,21 @@ in {
}; };
declarativeContents = mkOption { declarativeContents = mkOption {
type = with types; either lines (attrsOf lines); type = with types; attrsOf lines;
default = {}; default = {};
description = '' 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 All data will be erased when starting the LDAP server. Modifications
<emphasis>must</emphasis> be stored in the directory defined by to the database are not prevented, they are just dropped on the next
<code>dataDir</code>. Second, all <code>dataDir</code> will be erased reboot of the server. Performance-wise the database and indexes are
when starting the LDAP server. Third, modifications to the database rebuilt on each server startup, so this will slow down server startup,
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,
especially with large databases. especially with large databases.
''; '';
example = '' example = lib.literalExample ''
dn: dc=example,dc=org {
"dc=example,dc=org" = '''
dn= dn: dc=example,dc=org
objectClass: domain objectClass: domain
dc: example dc: example
@ -280,95 +232,41 @@ in {
ou: users ou: users
# ... # ...
''';
}
''; '';
}; };
}; };
}; };
meta = { meta.maintainers = with lib.maintainters; [ mic92 kwohlfahrt ];
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 { 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 = [{ assertions = [{
assertion = !(cfg.rootpwFile != null && cfg.rootpw != null); assertion = lib.length (lib.attrNames cfg.settings.children) >= 2 || cfg ? database;
message = "services.openldap: at most one of rootpw or rootpwFile must be set"; message = ''
No OpenLDAP database is defined. Configure one with `services.openldap.settings`
or `services.openldap.database` (legacy).
'';
}]; }];
environment.systemPackages = [ openldap ]; 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 = { services.openldap.settings = {
attrs = { attrs = {
objectClass = "olcGlobal"; objectClass = "olcGlobal";
cn = "config"; cn = "config";
olcPidFile = "/run/slapd/slapd.pid"; olcPidFile = "/run/slapd/slapd.pid";
} // (lib.optionalAttrs (cfg.logLevel != null) { };
olcLogLevel = cfg.logLevel;
});
children = { children = {
"cn=schema" = { "cn=schema" = {
attrs = { attrs = {
cn = "schema"; cn = "schema";
objectClass = "olcSchemaConfig"; 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 = { systemd.services.openldap = {
@ -376,10 +274,19 @@ in {
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
after = [ "network.target" ]; after = [ "network.target" ];
preStart = let 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; 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) dataDirs = lib.mapAttrs' (name: value: lib.nameValuePair value.attrs.olcSuffix value.attrs.olcDbDirectory)
(lib.filterAttrs (_: value: value.attrs ? olcDbDirectory) dbSettings); (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 '' in ''
mkdir -p /run/slapd mkdir -p /run/slapd
chown -R "${cfg.user}:${cfg.group}" /run/slapd chown -R "${cfg.user}:${cfg.group}" /run/slapd
@ -393,27 +300,7 @@ in {
'')} '')}
chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir} chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir}
${if types.lines.check cfg.declarativeContents ${lib.concatStrings (map mkLoadScript (lib.attrNames 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)}
'')
}
${openldap}/bin/slaptest -u -F ${lib.escapeShellArg configDir} ${openldap}/bin/slaptest -u -F ${lib.escapeShellArg configDir}
''; '';
serviceConfig = { serviceConfig = {

View File

@ -24,9 +24,6 @@ in {
environment.etc."openldap/root_password".text = "notapassword"; environment.etc."openldap/root_password".text = "notapassword";
services.openldap = { services.openldap = {
enable = true; enable = true;
defaultSchemas = null;
dataDir = null;
database = null;
settings = { settings = {
children = { children = {
"cn=schema" = { "cn=schema" = {
@ -61,17 +58,21 @@ in {
}; };
# Old-style configuration # Old-style configuration
shortOptions = import ./make-test-python.nix { oldOptions = import ./make-test-python.nix {
inherit testScript; inherit testScript;
name = "openldap"; name = "openldap";
machine = { pkgs, ... }: { machine = { pkgs, ... }: {
services.openldap = { services.openldap = {
enable = true; enable = true;
logLevel = "stats acl";
defaultSchemas = true;
database = "mdb";
suffix = "dc=example"; suffix = "dc=example";
rootdn = "cn=root,dc=example"; rootdn = "cn=root,dc=example";
rootpw = "notapassword"; rootpw = "notapassword";
declarativeContents = dbContents; dataDir = "/var/db/openldap";
declarativeContents."dc=example" = dbContents;
}; };
}; };
}; };
@ -84,10 +85,6 @@ in {
services.openldap = { services.openldap = {
enable = true; enable = true;
configDir = "/var/db/slapd.d"; configDir = "/var/db/slapd.d";
# Silence warnings
defaultSchemas = null;
dataDir = null;
database = null;
}; };
}; };