ldap: do not write password to the Nix store

This commit is contained in:
Antoine Eiche 2023-05-20 00:12:02 +02:00 committed by lewo
parent 33554e57ce
commit fb3210b932
5 changed files with 114 additions and 61 deletions

View File

@ -240,11 +240,11 @@ in
''; '';
}; };
password = mkOption { passwordFile = mkOption {
type = types.str; type = types.str;
example = "not$4f3"; example = "/run/my-secret";
description = '' description = ''
Password required to authenticate against the LDAP servers. A file containing the password required to authenticate against the LDAP servers.
''; '';
}; };
}; };

View File

@ -45,4 +45,25 @@ in
if value.hashedPasswordFile == null then if value.hashedPasswordFile == null then
builtins.toString (mkHashFile name value.hashedPassword) builtins.toString (mkHashFile name value.hashedPassword)
else value.hashedPasswordFile) cfg.loginAccounts; else value.hashedPasswordFile) cfg.loginAccounts;
# Appends the LDAP bind password to files to avoid writing this
# password into the Nix store.
appendLdapBindPwd = {
name, file, prefix, passwordFile, destination
}: pkgs.writeScript "append-ldap-bind-pwd-in-${name}" ''
#!${pkgs.stdenv.shell}
set -euo pipefail
baseDir=$(dirname ${destination})
if (! test -d "$baseDir"); then
mkdir -p $baseDir
chmod 755 $baseDir
fi
cat ${file} > ${destination}
echo -n "${prefix}" >> ${destination}
cat ${passwordFile} >> ${destination}
chmod 600 ${destination}
'';
} }

View File

@ -22,9 +22,10 @@ let
cfg = config.mailserver; cfg = config.mailserver;
passwdDir = "/run/dovecot2"; passwdDir = "/run/dovecot2";
passdbFile = "${passwdDir}/passdb"; passwdFile = "${passwdDir}/passwd";
userdbFile = "${passwdDir}/userdb"; userdbFile = "${passwdDir}/userdb";
# This file contains the ldap bind password
ldapConfFile = "${passwdDir}/dovecot-ldap.conf.ext";
bool2int = x: if x then "1" else "0"; bool2int = x: if x then "1" else "0";
maildirLayoutAppendix = lib.optionalString cfg.useFsLayout ":LAYOUT=fs"; maildirLayoutAppendix = lib.optionalString cfg.useFsLayout ":LAYOUT=fs";
@ -58,6 +59,41 @@ let
''; '';
}; };
ldapConfig = pkgs.writeTextFile {
name = "dovecot-ldap.conf.ext.template";
text = ''
ldap_version = 3
uris = ${lib.concatStringsSep " " cfg.ldap.uris}
${lib.optionalString cfg.ldap.startTls ''
tls = yes
''}
tls_require_cert = hard
tls_ca_cert_file = ${cfg.ldap.tlsCAFile}
dn = ${cfg.ldap.bind.dn}
sasl_bind = no
auth_bind = yes
base = ${cfg.ldap.searchBase}
scope = ${mkLdapSearchScope cfg.ldap.searchScope}
${lib.optionalString (cfg.ldap.dovecot.userAttrs != "") ''
user_attrs = ${cfg.ldap.dovecot.user_attrs}
''}
user_filter = ${cfg.ldap.dovecot.userFilter}
${lib.optionalString (cfg.ldap.dovecot.passAttrs != "") ''
pass_attrs = ${cfg.ldap.dovecot.passAttrs}
''}
pass_filter = ${cfg.ldap.dovecot.passFilter}
'';
};
setPwdInLdapConfFile = appendLdapBindPwd {
name = "ldap-conf-file";
file = ldapConfig;
prefix = "dnpass = ";
passwordFile = cfg.ldap.bind.passwordFile;
destination = ldapConfFile;
};
genPasswdScript = pkgs.writeScript "generate-password-file" '' genPasswdScript = pkgs.writeScript "generate-password-file" ''
#!${pkgs.stdenv.shell} #!${pkgs.stdenv.shell}
@ -75,7 +111,7 @@ let
fi fi
done done
cat <<EOF > ${passdbFile} cat <<EOF > ${passwdFile}
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: ${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value:
"${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::" "${name}:${"$(head -n 1 ${passwordFiles."${name}"})"}::::::"
) cfg.loginAccounts)} ) cfg.loginAccounts)}
@ -90,7 +126,7 @@ let
) cfg.loginAccounts)} ) cfg.loginAccounts)}
EOF EOF
chmod 600 ${passdbFile} chmod 600 ${passwdFile}
chmod 600 ${userdbFile} chmod 600 ${userdbFile}
''; '';
@ -233,7 +269,7 @@ in
passdb { passdb {
driver = passwd-file driver = passwd-file
args = ${passdbFile} args = ${passwdFile}
} }
userdb { userdb {
@ -245,12 +281,12 @@ in
${lib.optionalString cfg.ldap.enable '' ${lib.optionalString cfg.ldap.enable ''
passdb { passdb {
driver = ldap driver = ldap
args = /etc/dovecot/dovecot-ldap.conf.ext args = ${ldapConfFile}
} }
userdb { userdb {
driver = ldap driver = ldap
args = /etc/dovecot/dovecot-ldap.conf.ext args = ${ldapConfFile}
default_fields = home=/var/vmail/ldap/%u uid=${toString cfg.vmailUID} gid=${toString cfg.vmailUID} default_fields = home=/var/vmail/ldap/%u uid=${toString cfg.vmailUID} gid=${toString cfg.vmailUID}
} }
''} ''}
@ -317,37 +353,6 @@ in
''; '';
}; };
environment.etc = lib.optionalAttrs (cfg.ldap.enable) {
"dovecot/dovecot-ldap.conf.ext" = {
mode = "0600";
uid = config.ids.uids.dovecot2;
gid = config.ids.gids.dovecot2;
text = ''
ldap_version = 3
uris = ${lib.concatStringsSep " " cfg.ldap.uris}
${lib.optionalString cfg.ldap.startTls ''
tls = yes
''}
tls_require_cert = hard
tls_ca_cert_file = ${cfg.ldap.tlsCAFile}
dn = ${cfg.ldap.bind.dn}
dnpass = ${cfg.ldap.bind.password}
sasl_bind = no
auth_bind = yes
base = ${cfg.ldap.searchBase}
scope = ${mkLdapSearchScope cfg.ldap.searchScope}
${lib.optionalString (cfg.ldap.dovecot.userAttrs != "") ''
user_attrs = ${cfg.ldap.dovecot.user_attrs}
''}
user_filter = ${cfg.ldap.dovecot.userFilter}
${lib.optionalString (cfg.ldap.dovecot.passAttrs != "") ''
pass_attrs = ${cfg.ldap.dovecot.passAttrs}
''}
pass_filter = ${cfg.ldap.dovecot.passFilter}
'';
};
};
systemd.services.dovecot2 = { systemd.services.dovecot2 = {
preStart = '' preStart = ''
${genPasswdScript} ${genPasswdScript}
@ -358,10 +363,10 @@ in
${pkgs.dovecot_pigeonhole}/bin/sievec "$k" ${pkgs.dovecot_pigeonhole}/bin/sievec "$k"
done done
chown -R '${dovecot2Cfg.mailUser}:${dovecot2Cfg.mailGroup}' '${stateDir}/imap_sieve' chown -R '${dovecot2Cfg.mailUser}:${dovecot2Cfg.mailGroup}' '${stateDir}/imap_sieve'
''; '' + (lib.optionalString cfg.ldap.enable setPwdInLdapConfFile);
}; };
systemd.services.postfix.restartTriggers = [ genPasswdScript ]; systemd.services.postfix.restartTriggers = [ genPasswdScript ] ++ (lib.optional cfg.ldap.enable [setPwdInLdapConfFile]);
systemd.services.dovecot-fts-xapian-optimize = lib.mkIf (cfg.fullTextSearch.enable && cfg.fullTextSearch.maintenance.enable) { systemd.services.dovecot-fts-xapian-optimize = lib.mkIf (cfg.fullTextSearch.enable && cfg.fullTextSearch.maintenance.enable) {
description = "Optimize dovecot indices for fts_xapian"; description = "Optimize dovecot indices for fts_xapian";

View File

@ -133,13 +133,13 @@ let
smtpd_sasl_security_options = "noanonymous"; smtpd_sasl_security_options = "noanonymous";
smtpd_sasl_local_domain = "$myhostname"; smtpd_sasl_local_domain = "$myhostname";
smtpd_client_restrictions = "permit_sasl_authenticated,reject"; smtpd_client_restrictions = "permit_sasl_authenticated,reject";
smtpd_sender_login_maps = "hash:/etc/postfix/vaccounts${lib.optionalString cfg.ldap.enable ",ldap:${ldapSenderLoginMap}"}"; smtpd_sender_login_maps = "hash:/etc/postfix/vaccounts${lib.optionalString cfg.ldap.enable ",ldap:${ldapSenderLoginMapFile}"}";
smtpd_sender_restrictions = "reject_sender_login_mismatch"; smtpd_sender_restrictions = "reject_sender_login_mismatch";
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject"; smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
cleanup_service_name = "submission-header-cleanup"; cleanup_service_name = "submission-header-cleanup";
}; };
commonLdapConfig = lib.optionalString (cfg.ldap.enable) '' commonLdapConfig = ''
server_host = ${lib.concatStringsSep " " cfg.ldap.uris} server_host = ${lib.concatStringsSep " " cfg.ldap.uris}
start_tls = ${if cfg.ldap.startTls then "yes" else "no"} start_tls = ${if cfg.ldap.startTls then "yes" else "no"}
version = 3 version = 3
@ -151,26 +151,47 @@ let
bind = yes bind = yes
bind_dn = ${cfg.ldap.bind.dn} bind_dn = ${cfg.ldap.bind.dn}
bind_pw = ${cfg.ldap.bind.password}
''; '';
ldapSenderLoginMap = lib.optionalString (cfg.ldap.enable) ldapSenderLoginMap = pkgs.writeText "ldap-sender-login-map.cf" ''
(pkgs.writeText "ldap-sender-login-map.cf" ''
${commonLdapConfig} ${commonLdapConfig}
query_filter = ${cfg.ldap.postfix.filter} query_filter = ${cfg.ldap.postfix.filter}
result_attribute = ${cfg.ldap.postfix.mailAttribute} result_attribute = ${cfg.ldap.postfix.mailAttribute}
''); '';
ldapSenderLoginMapFile = "/run/postfix/ldap-sender-login-map.cf";
appendPwdInSenderLoginMap = appendLdapBindPwd {
name = "ldap-sender-login-map";
file = ldapSenderLoginMap;
prefix = "bind_pw = ";
passwordFile = cfg.ldap.bind.passwordFile;
destination = ldapSenderLoginMapFile;
};
ldapVirtualMailboxMap = lib.optionalString (cfg.ldap.enable) ldapVirtualMailboxMap = pkgs.writeText "ldap-virtual-mailbox-map.cf" ''
(pkgs.writeText "ldap-virtual-mailbox-map.cf" ''
${commonLdapConfig} ${commonLdapConfig}
query_filter = ${cfg.ldap.postfix.filter} query_filter = ${cfg.ldap.postfix.filter}
result_attribute = ${cfg.ldap.postfix.uidAttribute} result_attribute = ${cfg.ldap.postfix.uidAttribute}
''); '';
ldapVirtualMailboxMapFile = "/run/postfix/ldap-virtual-mailbox-map.cf";
appendPwdInVirtualMailboxMap = appendLdapBindPwd {
name = "ldap-virtual-mailbox-map";
file = ldapVirtualMailboxMap;
prefix = "bind_pw = ";
passwordFile = cfg.ldap.bind.passwordFile;
destination = ldapVirtualMailboxMapFile;
};
in in
{ {
config = with cfg; lib.mkIf enable { config = with cfg; lib.mkIf enable {
systemd.services.postfix-setup = lib.mkIf cfg.ldap.enable {
preStart = ''
${appendPwdInVirtualMailboxMap}
${appendPwdInSenderLoginMap}
'';
restartTriggers = [ appendPwdInVirtualMailboxMap appendPwdInSenderLoginMap ];
};
services.postfix = { services.postfix = {
enable = true; enable = true;
hostname = "${sendingFqdn}"; hostname = "${sendingFqdn}";
@ -202,7 +223,7 @@ in
virtual_mailbox_maps = [ virtual_mailbox_maps = [
(mappedFile "valias") (mappedFile "valias")
] ++ lib.optionals (cfg.ldap.enable) [ ] ++ lib.optionals (cfg.ldap.enable) [
"ldap:${ldapVirtualMailboxMap}" "ldap:${ldapVirtualMailboxMapFile}"
]; ];
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp"; virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
# Avoid leakage of X-Original-To, X-Delivered-To headers between recipients # Avoid leakage of X-Original-To, X-Delivered-To headers between recipients

View File

@ -28,6 +28,8 @@ pkgs.nixosTest {
${pkgs.python3}/bin/python ${../scripts/mail-check.py} $@ ${pkgs.python3}/bin/python ${../scripts/mail-check.py} $@
'')]; '')];
environment.etc.bind-password.text = bindPassword;
services.openldap = { services.openldap = {
enable = true; enable = true;
settings = { settings = {
@ -45,7 +47,7 @@ pkgs.nixosTest {
"olcMdbConfig" "olcMdbConfig"
]; ];
olcDatabase = "{1}mdb"; olcDatabase = "{1}mdb";
olcDbDirectory = "/var/lib/openldap"; olcDbDirectory = "/var/lib/openldap/example";
olcSuffix = "dc=example"; olcSuffix = "dc=example";
}; };
}; };
@ -96,7 +98,7 @@ pkgs.nixosTest {
]; ];
bind = { bind = {
dn = "cn=mail,dc=example"; dn = "cn=mail,dc=example";
password = bindPassword; passwordFile = "/etc/bind-password";
}; };
searchBase = "ou=users,dc=example"; searchBase = "ou=users,dc=example";
searchScope = "sub"; searchScope = "sub";
@ -141,6 +143,10 @@ pkgs.nixosTest {
machine.succeed("doveadm user -u alice@example.com") machine.succeed("doveadm user -u alice@example.com")
machine.succeed("doveadm user -u bob@example.com") machine.succeed("doveadm user -u bob@example.com")
with subtest("Files containing secrets are only readable by root"):
machine.succeed("ls -l /run/postfix/*.cf | grep -e '-rw------- 1 root root'")
machine.succeed("ls -l /run/dovecot2/dovecot-ldap.conf.ext | grep -e '-rw------- 1 root root'")
with subtest("Test account/mail address binding"): with subtest("Test account/mail address binding"):
machine.fail(" ".join([ machine.fail(" ".join([
"mail-check send-and-read", "mail-check send-and-read",