diff options
author | Kai Wohlfahrt <kai.wohlfahrt@gmail.com> | 2022-06-05 00:52:54 +0100 |
---|---|---|
committer | Jörg Thalheim <joerg@thalheim.io> | 2022-06-29 19:59:29 +0200 |
commit | fd7d901133f9fbfc893cdb33f7d630846bb21f9c (patch) | |
tree | ee1d9cd08070f49c53e473b2315f45fedf6f39c4 | |
parent | 38ead944cee78c8ee5543067b3ec839bbb36eed6 (diff) | |
download | nixpkgs-fd7d901133f9fbfc893cdb33f7d630846bb21f9c.tar nixpkgs-fd7d901133f9fbfc893cdb33f7d630846bb21f9c.tar.gz nixpkgs-fd7d901133f9fbfc893cdb33f7d630846bb21f9c.tar.bz2 nixpkgs-fd7d901133f9fbfc893cdb33f7d630846bb21f9c.tar.lz nixpkgs-fd7d901133f9fbfc893cdb33f7d630846bb21f9c.tar.xz nixpkgs-fd7d901133f9fbfc893cdb33f7d630846bb21f9c.tar.zst nixpkgs-fd7d901133f9fbfc893cdb33f7d630846bb21f9c.zip |
openldap: run under systemd-defined user/group
This improves security, by starting the service as an unprivileged user, rather than starting as root and relying on the service to drop privileges. This requires a significant cleanup of pre-init scripts, to make use of StateDirectory and RuntimeDirectory for permissions.
-rw-r--r-- | nixos/modules/services/databases/openldap.nix | 127 | ||||
-rw-r--r-- | nixos/tests/openldap.nix | 2 |
2 files changed, 89 insertions, 40 deletions
diff --git a/nixos/modules/services/databases/openldap.nix b/nixos/modules/services/databases/openldap.nix index 1d712987a56..45242b2b48f 100644 --- a/nixos/modules/services/databases/openldap.nix +++ b/nixos/modules/services/databases/openldap.nix @@ -10,7 +10,15 @@ let # Can't do types.either with multiple non-overlapping submodules, so define our own singleLdapValueType = lib.mkOptionType rec { name = "LDAP"; - description = "LDAP value"; + # TODO: It would be nice to define a { secret = ...; } option, using + # systemd's LoadCredentials for secrets. That would remove the last + # barrier to using DynamicUser for openldap. This is blocked on + # systemd/systemd#19604 + description = '' + LDAP value - either a string, or an attrset containing + <literal>path</literal> or <literal>base64</literal> for included + values or base-64 encoded values respectively. + ''; check = x: lib.isString x || (lib.isAttrs x && (x ? path || x ? base64)); merge = lib.mergeEqualOption; }; @@ -80,9 +88,7 @@ in { enable = mkOption { type = types.bool; default = false; - description = " - Whether to enable the ldap server. - "; + description = "Whether to enable the ldap server."; }; package = mkOption { @@ -147,7 +153,7 @@ in { attrs = { objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ]; olcDatabase = "{1}mdb"; - olcDbDirectory = "/var/db/ldap"; + olcDbDirectory = "/var/lib/openldap/ldap"; olcDbIndex = [ "objectClass eq" "cn pres,eq" @@ -171,7 +177,18 @@ in { Use this config directory instead of generating one from the <literal>settings</literal> option. Overrides all NixOS settings. ''; - example = "/var/db/slapd.d"; + example = "/var/lib/openldap/slapd.d"; + }; + + mutableConfig = mkOption { + type = types.bool; + default = false; + description = '' + Whether to allow writable on-line configuration. If + <literal>true</literal>, the NixOS settings will only be used to + initialize the OpenLDAP configuration if it does not exist, and are + subsequently ignored. + ''; }; declarativeContents = mkOption { @@ -185,6 +202,11 @@ in { reboot of the server. Performance-wise the database and indexes are rebuilt on each server startup, so this will slow down server startup, especially with large databases. + + Note that the root of the DB must be defined in + <code>services.openldap.settings</code> and the + <code>olcDbDirectory</code> must begin with + <literal>"/var/lib/openldap"</literal>. ''; example = lib.literalExpression '' { @@ -207,7 +229,49 @@ in { meta.maintainers = with lib.maintainers; [ mic92 kwohlfahrt ]; - config = mkIf cfg.enable { + config = let + dbSettings = mapAttrs' (name: { attrs, ... }: nameValuePair attrs.olcSuffix attrs) + (filterAttrs (name: value: hasPrefix "olcDatabase=" name) cfg.settings.children); + settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings)); + writeConfig = pkgs.writeShellScript "openldap-config" '' + set -euo pipefail + + ${lib.optionalString (!cfg.mutableConfig) "rm -rf ${configDir}/*"} + if [ ! -e "${configDir}/cn=config.ldif" ]; then + ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} + fi + chmod -R ${if cfg.mutableConfig then "u+rw" else "u+r-w"} ${configDir} + ''; + + contentsFiles = mapAttrs (dn: ldif: pkgs.writeText "${dn}.ldif" ldif) cfg.declarativeContents; + writeContents = pkgs.writeShellScript "openldap-load" '' + rm -rf /var/lib/openldap/$2/* + ${openldap}/bin/slapadd -F ${configDir} -b $1 -l $3 + ''; + in mkIf cfg.enable { + assertions = [{ + assertion = (cfg.declarativeContents != {}) -> cfg.configDir == null; + message = '' + Declarative DB contents (${attrNames cfg.declarativeContents}) are not + supported with user-managed configuration. + ''; + }] ++ (map (dn: { + assertion = (getAttr dn dbSettings) ? "olcDbDirectory"; + # olcDbDirectory is necessary to prepopulate database using `slapadd`. + message = '' + Declarative DB ${dn} does not exist in `services.openldap.settings`, or does not have + `olcDbDirectory` configured. + ''; + }) (attrNames cfg.declarativeContents)) ++ (mapAttrsToList (dn: { olcDbDirectory ? null, ... }: { + # For forward compatibility with `DynamicUser`, and to avoid accidentally clobbering + # directories with `declarativeContents`. + assertion = (olcDbDirectory != null) -> + ((hasPrefix "/var/lib/openldap/" olcDbDirectory) && (olcDbDirectory != "/var/lib/openldap/")); + message = '' + Database ${dn} has `olcDbDirectory` (${olcDbDirectory}) that is not a subdirectory of + `/var/lib/openldap/`. + ''; + }) dbSettings); environment.systemPackages = [ openldap ]; # Literal attributes must always be set @@ -231,46 +295,31 @@ in { ]; wantedBy = [ "multi-user.target" ]; after = [ "network-online.target" ]; - preStart = let - settingsFile = pkgs.writeText "config.ldif" (lib.concatStringsSep "\n" (attrsToLdif "cn=config" cfg.settings)); - - dbSettings = lib.filterAttrs (name: value: lib.hasPrefix "olcDatabase=" name) cfg.settings.children; - dataDirs = lib.mapAttrs' (name: value: lib.nameValuePair value.attrs.olcSuffix value.attrs.olcDbDirectory) - (lib.filterAttrs (_: value: value.attrs ? olcDbDirectory) dbSettings); - dataFiles = lib.mapAttrs (dn: contents: pkgs.writeText "${dn}.ldif" contents) cfg.declarativeContents; - mkLoadScript = dn: let - dataDir = lib.escapeShellArg (getAttr dn dataDirs); - in '' - rm -rf ${dataDir}/* - ${openldap}/bin/slapadd -F ${lib.escapeShellArg configDir} -b ${dn} -l ${getAttr dn dataFiles} - chown -R "${cfg.user}:${cfg.group}" ${dataDir} - ''; - in '' - mkdir -p /run/slapd - chown -R "${cfg.user}:${cfg.group}" /run/slapd - - mkdir -p ${lib.escapeShellArg configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)} - chown "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir} ${lib.escapeShellArgs (lib.attrValues dataDirs)} - - ${lib.optionalString (cfg.configDir == null) ('' - rm -Rf ${configDir}/* - ${openldap}/bin/slapadd -F ${configDir} -bcn=config -l ${settingsFile} - '')} - chown -R "${cfg.user}:${cfg.group}" ${lib.escapeShellArg configDir} - - ${lib.concatStrings (map mkLoadScript (lib.attrNames cfg.declarativeContents))} - ${openldap}/bin/slaptest -u -F ${lib.escapeShellArg configDir} - ''; serviceConfig = { + User = cfg.user; + Group = cfg.group; + ExecStartPre = [ + "!${pkgs.coreutils}/bin/mkdir -p ${configDir}" + "+${pkgs.coreutils}/bin/chown $USER ${configDir}" + ] ++ (lib.optional (cfg.configDir == null) writeConfig) + ++ (mapAttrsToList (dn: content: lib.escapeShellArgs [ + writeContents dn (getAttr dn dbSettings).olcDbDirectory content + ]) contentsFiles) + ++ [ "${openldap}/bin/slaptest -u -F ${configDir}" ]; ExecStart = lib.escapeShellArgs ([ - "${openldap}/libexec/slapd" "-d" "0" "-u" cfg.user "-g" cfg.group "-F" configDir - "-h" (lib.concatStringsSep " " cfg.urlList) + "${openldap}/libexec/slapd" "-d" "0" "-F" configDir "-h" (lib.concatStringsSep " " cfg.urlList) ]); Type = "notify"; # Fixes an error where openldap attempts to notify from a thread # outside the main process: # Got notification message from PID 6378, but reception only permitted for main PID 6377 NotifyAccess = "all"; + RuntimeDirectory = "slapd"; # TODO: openldap, for consistency + StateDirectory = ["openldap"] + ++ (map ({olcDbDirectory, ... }: removePrefix "/var/lib/" olcDbDirectory) (attrValues dbSettings)); + StateDirectoryMode = "700"; + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; + CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; }; }; diff --git a/nixos/tests/openldap.nix b/nixos/tests/openldap.nix index 13afe166b9b..43d5e0d4a1a 100644 --- a/nixos/tests/openldap.nix +++ b/nixos/tests/openldap.nix @@ -43,7 +43,7 @@ in { attrs = { objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ]; olcDatabase = "{1}mdb"; - olcDbDirectory = "/var/db/openldap"; + olcDbDirectory = "/var/lib/openldap/db"; olcSuffix = "dc=example"; olcRootDN = { # cn=root,dc=example |