ldap: do not write password to the Nix store
This commit is contained in:
parent
f7a800ff8c
commit
870b1d1187
5 changed files with 114 additions and 61 deletions
|
@ -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.
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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}
|
||||||
|
'';
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
@ -226,7 +262,7 @@ in
|
||||||
|
|
||||||
passdb {
|
passdb {
|
||||||
driver = passwd-file
|
driver = passwd-file
|
||||||
args = ${passdbFile}
|
args = ${passwdFile}
|
||||||
}
|
}
|
||||||
|
|
||||||
userdb {
|
userdb {
|
||||||
|
@ -238,12 +274,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}
|
||||||
}
|
}
|
||||||
''}
|
''}
|
||||||
|
@ -310,37 +346,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}
|
||||||
|
@ -351,10 +356,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";
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in a new issue