Add support for LDAP users
Allow configuring lookups for users and their mail addresses from an LDAP directory. The LDAP username will be used as an accountname as opposed to the email address used as the `loginName` for declarative accounts. Mailbox for LDAP users will be stored below `/var/vmail/ldap/<account>`. Configuring domains is out of scope, since domains require further configuration within the NixOS mailserver construct to set up all related services accordingly. Aliases can already be configured using `mailserver.forwards` but could be supported using LDAP at a later point.
This commit is contained in:
parent
290d00f6db
commit
93a6542ff7
3 changed files with 235 additions and 2 deletions
150
default.nix
150
default.nix
|
@ -198,6 +198,156 @@ in
|
||||||
default = {};
|
default = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
ldap = {
|
||||||
|
enable = mkEnableOption "LDAP support";
|
||||||
|
|
||||||
|
uris = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
example = literalExpression ''
|
||||||
|
[
|
||||||
|
"ldaps://ldap1.example.com"
|
||||||
|
"ldaps://ldap2.example.com"
|
||||||
|
]
|
||||||
|
'';
|
||||||
|
description = ''
|
||||||
|
URIs where your LDAP server can be reached
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
startTls = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = ''
|
||||||
|
Whether to enable StartTLS upon connection to the server.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
tlsCAFile = mkOption {
|
||||||
|
type = types.path;
|
||||||
|
default = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
|
||||||
|
description = ''
|
||||||
|
Certifificate trust anchors used to verify the LDAP server certificate.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
bind = {
|
||||||
|
dn = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
example = "cn=mail,ou=accounts,dc=example,dc=com";
|
||||||
|
description = ''
|
||||||
|
Distinguished name used by the mail server to do lookups
|
||||||
|
against the LDAP servers.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
password = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
example = "not$4f3";
|
||||||
|
description = ''
|
||||||
|
Password required to authenticate against the LDAP servers.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
searchBase = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
example = "ou=people,ou=accounts,dc=example,dc=com";
|
||||||
|
description = ''
|
||||||
|
Base DN at below which to search for users accounts.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
searchScope = mkOption {
|
||||||
|
type = types.enum [ "sub" "base" "one" ];
|
||||||
|
default = "sub";
|
||||||
|
description = ''
|
||||||
|
Search scope below which users accounts are looked for.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
dovecot = {
|
||||||
|
userAttrs = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
description = ''
|
||||||
|
LDAP attributes to be retrieved during userdb lookups.
|
||||||
|
|
||||||
|
See the users_attrs reference at
|
||||||
|
https://doc.dovecot.org/configuration_manual/authentication/ldap_settings_auth/#user-attrs
|
||||||
|
in the Dovecot manual.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
userFilter = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "cn=%u";
|
||||||
|
example = "(&(objectClass=inetOrgPerson)(cn=%u))";
|
||||||
|
description = ''
|
||||||
|
Filter for user lookups in Dovecot.
|
||||||
|
|
||||||
|
See the user_filter reference at
|
||||||
|
https://doc.dovecot.org/configuration_manual/authentication/ldap_settings_auth/#user-filter
|
||||||
|
in the Dovecot manual.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
passAttrs = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "userPassword=password";
|
||||||
|
description = ''
|
||||||
|
LDAP attributes to be retrieved during passdb lookups.
|
||||||
|
|
||||||
|
See the pass_attrs reference at
|
||||||
|
https://doc.dovecot.org/configuration_manual/authentication/ldap_settings_auth/#pass-attrs
|
||||||
|
in the Dovecot manual.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
passFilter = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "cn=%u";
|
||||||
|
example = "(&(objectClass=inetOrgPerson)(cn=%u))";
|
||||||
|
description = ''
|
||||||
|
Filter for password lookups in Dovecot.
|
||||||
|
|
||||||
|
See the pass_filter reference for
|
||||||
|
https://doc.dovecot.org/configuration_manual/authentication/ldap_settings_auth/#pass-filter
|
||||||
|
in the Dovecot manual.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
postfix = {
|
||||||
|
filter = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "mail=%s";
|
||||||
|
example = "(&(objectClass=inetOrgPerson)(mail=%s))";
|
||||||
|
description = ''
|
||||||
|
LDAP filter used to search for an account by mail, where
|
||||||
|
<literal>%s</literal> is a substitute for the address in
|
||||||
|
question.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
uidAttribute = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "cn";
|
||||||
|
example = "uid";
|
||||||
|
description = ''
|
||||||
|
The LDAP attribute referencing the account name for a user.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
mailAttribute = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "mail";
|
||||||
|
description = ''
|
||||||
|
The LDAP attribute holding mail addresses for a user.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
indexDir = mkOption {
|
indexDir = mkOption {
|
||||||
type = types.nullOr types.str;
|
type = types.nullOr types.str;
|
||||||
default = null;
|
default = null;
|
||||||
|
|
|
@ -99,6 +99,12 @@ let
|
||||||
# The assertion garantees there is exactly one Junk mailbox.
|
# The assertion garantees there is exactly one Junk mailbox.
|
||||||
junkMailboxName = if junkMailboxNumber == 1 then builtins.elemAt junkMailboxes 0 else "";
|
junkMailboxName = if junkMailboxNumber == 1 then builtins.elemAt junkMailboxes 0 else "";
|
||||||
|
|
||||||
|
mkLdapSearchScope = scope: (
|
||||||
|
if scope == "sub" then "subtree"
|
||||||
|
else if scope == "one" then "onelevel"
|
||||||
|
else scope
|
||||||
|
);
|
||||||
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = with cfg; lib.mkIf enable {
|
config = with cfg; lib.mkIf enable {
|
||||||
|
@ -229,6 +235,19 @@ in
|
||||||
default_fields = uid=${builtins.toString cfg.vmailUID} gid=${builtins.toString cfg.vmailUID} home=${cfg.mailDirectory}
|
default_fields = uid=${builtins.toString cfg.vmailUID} gid=${builtins.toString cfg.vmailUID} home=${cfg.mailDirectory}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
${lib.optionalString cfg.ldap.enable ''
|
||||||
|
passdb {
|
||||||
|
driver = ldap
|
||||||
|
args = /etc/dovecot/dovecot-ldap.conf.ext
|
||||||
|
}
|
||||||
|
|
||||||
|
userdb {
|
||||||
|
driver = ldap
|
||||||
|
args = /etc/dovecot/dovecot-ldap.conf.ext
|
||||||
|
default_fields = home=/var/vmail/ldap/%u uid=${toString cfg.vmailUID} gid=${toString cfg.vmailUID}
|
||||||
|
}
|
||||||
|
''}
|
||||||
|
|
||||||
service auth {
|
service auth {
|
||||||
unix_listener auth {
|
unix_listener auth {
|
||||||
mode = 0660
|
mode = 0660
|
||||||
|
@ -291,6 +310,37 @@ 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}
|
||||||
|
|
|
@ -133,11 +133,40 @@ 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";
|
smtpd_sender_login_maps = "hash:/etc/postfix/vaccounts${lib.optionalString cfg.ldap.enable ",ldap:${ldapSenderLoginMap}"}";
|
||||||
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) ''
|
||||||
|
server_host = ${lib.concatStringsSep " " cfg.ldap.uris}
|
||||||
|
start_tls = ${if cfg.ldap.startTls then "yes" else "no"}
|
||||||
|
version = 3
|
||||||
|
tls_ca_cert_file = ${cfg.ldap.tlsCAFile}
|
||||||
|
tls_require_cert = yes
|
||||||
|
|
||||||
|
search_base = ${cfg.ldap.searchBase}
|
||||||
|
scope = ${cfg.ldap.searchScope}
|
||||||
|
|
||||||
|
bind = yes
|
||||||
|
bind_dn = ${cfg.ldap.bind.dn}
|
||||||
|
bind_pw = ${cfg.ldap.bind.password}
|
||||||
|
'';
|
||||||
|
|
||||||
|
ldapSenderLoginMap = lib.optionalString (cfg.ldap.enable)
|
||||||
|
(pkgs.writeText "ldap-sender-login-map.cf" ''
|
||||||
|
${commonLdapConfig}
|
||||||
|
query_filter = ${cfg.ldap.postfix.filter}
|
||||||
|
result_attribute = ${cfg.ldap.postfix.uidAttribute}
|
||||||
|
'');
|
||||||
|
|
||||||
|
ldapVirtualMailboxMap = lib.optionalString (cfg.ldap.enable)
|
||||||
|
(pkgs.writeText "ldap-virtual-mailbox-map.cf" ''
|
||||||
|
${commonLdapConfig}
|
||||||
|
query_filter = ${cfg.ldap.postfix.filter}
|
||||||
|
result_attribute = ${cfg.ldap.postfix.uidAttribute}
|
||||||
|
'');
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
config = with cfg; lib.mkIf enable {
|
config = with cfg; lib.mkIf enable {
|
||||||
|
@ -170,7 +199,11 @@ in
|
||||||
virtual_gid_maps = "static:5000";
|
virtual_gid_maps = "static:5000";
|
||||||
virtual_mailbox_base = mailDirectory;
|
virtual_mailbox_base = mailDirectory;
|
||||||
virtual_mailbox_domains = vhosts_file;
|
virtual_mailbox_domains = vhosts_file;
|
||||||
virtual_mailbox_maps = mappedFile "valias";
|
virtual_mailbox_maps = [
|
||||||
|
(mappedFile "valias")
|
||||||
|
] ++ lib.optionals (cfg.ldap.enable) [
|
||||||
|
"ldap:${ldapVirtualMailboxMap}"
|
||||||
|
];
|
||||||
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
|
||||||
lmtp_destination_recipient_limit = "1";
|
lmtp_destination_recipient_limit = "1";
|
||||||
|
|
Loading…
Reference in a new issue