LDAP with password

This commit is contained in:
niten 2023-09-21 18:01:44 -07:00
parent 2ebd024977
commit 5b7eb05635
4 changed files with 244 additions and 122 deletions

View File

@ -54,7 +54,6 @@ in {
description = "Port on which to serve metrics data."; description = "Port on which to serve metrics data.";
default = 5034; default = 5034;
}; };
}; };
mail-user = mkOption { mail-user = mkOption {
@ -153,7 +152,7 @@ in {
specialUse = "Sent"; specialUse = "Sent";
}; };
Archive = { Archive = {
atuo = "no"; auto = "no";
specialUse = "Archive"; specialUse = "Archive";
}; };
Flagged = { Flagged = {
@ -180,44 +179,9 @@ in {
default = 5; default = 5;
}; };
ldap = let ldap-conf = mkOption {
ldapOpts = {
options = {
host = mkOption {
type = str; type = str;
description = "LDAP hostname."; description = "Path to LDAP dovecot2 configuration.";
};
port = mkOption {
type = str;
description = "Port on which LDAP is listening.";
};
base = mkOption {
type = str;
description = "Base of the LDAP server database.";
example = "dc=mydomain,dc=org";
};
bind-dn = mkOption {
type = str;
description = ''
DN used for fetching user information.
Needs access to homeDirectory, uidNumber, gidNumber, and uid, but not
password attributes.
'';
};
bind-password-file = mkOption {
type = str;
description = "Path to file containing bind password for bind-dn.";
};
};
};
in mkOption {
type = nullOr ldapOpts;
default = null;
}; };
}; };
@ -240,7 +204,7 @@ in {
group = cfg.mail-group; group = cfg.mail-group;
}; };
"${cfg.metrics.user}" = { "${cfg.metrics.user}" = {
isSystemUser = true; isySstemUser = true;
group = cfg.metrics.group; group = cfg.metrics.group;
}; };
}; };
@ -306,6 +270,9 @@ in {
name = "rspam_pipe_bin"; name = "rspam_pipe_bin";
paths = [ learnHam learnSpam ]; paths = [ learnHam learnSpam ];
}; };
mailUserUid = config.users.users."${cfg.mail-user}".uid;
mailUserGid = config.users.group."${cfg.mail-group}".gid;
in '' in ''
## Extra Config ## Extra Config
@ -331,6 +298,8 @@ in {
# When looking up usernames, just use the name, not the full address # When looking up usernames, just use the name, not the full address
auth_username_format = %n auth_username_format = %n
auth_mechanisms = login plain
service lmtp { service lmtp {
# Enable logging in debug mode # Enable logging in debug mode
${optionalString cfg.debug "executable = lmtp -L"} ${optionalString cfg.debug "executable = lmtp -L"}
@ -346,14 +315,13 @@ in {
# user = root # user = root
} }
auth_mechanisms = login plain
${optionalString (cfg.dovecot.ldap != null) ''
passdb { passdb {
driver = ldap driver = ldap
args = ${cfg.dovecot.ldap.generated-ldap-config} args = ${cfg.ldap-conf}
} }
''}
# All users map to one actual system user
userdb { userdb {
driver = static driver = static
args = uid=${ args = uid=${
@ -361,23 +329,6 @@ in {
} home=${cfg.state-directory}/mail/%u } home=${cfg.state-directory}/mail/%u
} }
# Used by postfix to authorize users
service auth {
inet_listener auth {
address = 0.0.0.0
port = ${toString cfg.ports.auth}
}
inet_listener auth-userdb {
address = 0.0.0.0
port = ${toString cfg.ports.userdb}
}
}
service auth-worker {
user = ${config.services.dovecot2.user}
idle_kill = 3
}
service imap { service imap {
vsz_limit = 1024M vsz_limit = 1024M
} }
@ -402,7 +353,7 @@ in {
imapsieve_mailbox2_causes = COPY imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:${sievePath}/report-ham.sieve imapsieve_mailbox2_before = file:${sievePath}/report-ham.sieve
sieve_pipe_bin_dir = ${pipeBin}/pipe/bin sieve_pipe_bin_dir = ${pipeBin}/bin
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
} }

View File

@ -75,6 +75,44 @@ in {
}; };
}; };
fudo.secrets.host-secrets."${hostname}" = {
dovecotLdapConfig = {
source-file = pkgs.writeText "dovecot-ldap.conf"
(concatStringsSep "\n" [
"uris = ldap://ldap-proxymine:${ldapPort}"
"ldap_version = 3"
"dn = ${cfg.ldap.bind-dn}"
"dnpass = ${readFile cfg.ldap.bind-password-file}"
"auth_bind = yes"
"auth_bind_userdn = uid=%u,${cfg.ldap.member-ou},${cfg.ldap.base}"
"base = ${cfg.ldap.base}"
]);
target-file = "/run/dovecot-secret/ldap.conf";
};
};
users.users = {
mailserver-dovecot = {
uid = 4455;
isSystemUser = true;
};
mailserver-antivirus = {
uid = 4456;
isSystemUser = true;
};
mailserver-dkim = {
uid = 4457;
isSystemUser = true;
};
};
systemd.tmpfiles.rules = [
"d ${cfg.state-directory}/dovecot 0700 mailserver-dovecot - - -"
"d ${cfg.state-directory}/antivirus 0700 mailserver-antivirus - - -"
"d ${cfg.state-directory}/dkim 0700 mailserver-dkim - - -"
];
virtualisation.arion.projects.mail-server.settings = let virtualisation.arion.projects.mail-server.settings = let
image = { pkgs, ... }: { image = { pkgs, ... }: {
project.name = "fudo-mailserver"; project.name = "fudo-mailserver";
@ -89,6 +127,10 @@ in {
authPort = 5447; authPort = 5447;
userdbPort = 5448; userdbPort = 5448;
metricsPort = 5034; metricsPort = 5034;
mkUserMap = username:
let uid = config.users.users."${username}".uid;
in "${uid}:${uid}";
in { in {
smtp = { smtp = {
networks = [ networks = [
@ -96,14 +138,19 @@ in {
# Needs access to internet to forward emails # Needs access to internet to forward emails
"external_network" "external_network"
]; ];
volumes = [
"${hostSecrets.dovecotLdapConfig.target-file}:/run/dovecot2/conf.d/ldap.conf:ro"
];
ports = [ "25:25" "587:587" "465:465" "2525:2525" ]; ports = [ "25:25" "587:587" "465:465" "2525:2525" ];
nixos = { nixos = {
useSystemd = true; useSystemd = true;
configuration = [ configuration = [
(import ./postfix.nix) (import ./postfix.nix)
(import ./dovecot.nix)
{ {
boot.tmpOnTmpfs = true; boot.tmpOnTmpfs = true;
system.nssModules = lib.mkForce [ ]; system.nssModules = lib.mkForce [ ];
fudo.mail.postfix = { fudo.mail.postfix = {
enable = true; enable = true;
debug = cfg.debug; debug = cfg.debug;
@ -127,6 +174,19 @@ in {
sasl-domain = cfg.sasl-domain; sasl-domain = cfg.sasl-domain;
message-size-limit = cfg.message-size-limit; message-size-limit = cfg.message-size-limit;
ports = { metrics = metricsPort; }; ports = { metrics = metricsPort; };
rspamd-server = {
host = "antispam";
port = antispamPort;
};
lmtp-server = {
host = "imap";
port = lmtpPort;
};
dkim-server = {
host = "dkim";
port = dkimPort;
};
ldap-conf = "/run/dovecot2/conf.d/ldap.conf";
}; };
} }
]; ];
@ -135,6 +195,11 @@ in {
imap = { imap = {
networks = [ "internal_network" ]; networks = [ "internal_network" ];
ports = [ "143:143" "993:993" ]; ports = [ "143:143" "993:993" ];
user = mkUserMap "mailserver-dovecot";
volumes = [
"${cfg.state-directory}/dovecot:/state"
"${hostSecrets.dovecotLdapConfig.target-file}:/run/dovecot2/conf.d/ldap.conf:ro"
];
nixos = { nixos = {
useSystemd = true; useSystemd = true;
configuration = [ configuration = [
@ -145,7 +210,7 @@ in {
fudo.mail.dovecot = { fudo.mail.dovecot = {
enable = true; enable = true;
debug = cfg.debug; debug = cfg.debug;
state-directory = "${cfg.state-directory}/dovecot"; state-directory = "/state";
ports = { ports = {
lmtp = lmtpPort; lmtp = lmtpPort;
auth = authPort; auth = authPort;
@ -162,13 +227,7 @@ in {
host = "antispam"; host = "antispam";
port = antispamPort; port = antispamPort;
}; };
ldap = mkIf cfg.ldap-proxy { ldap-conf = "/run/dovecot2/conf.d/ldap.conf";
host = "ldap-proxy";
port = 3389;
base = cfg.ldap.base;
bind-dn = cfg.ldap.bind-dn;
bind-password-file = cfg.ldap.bind-password-file;
};
}; };
} }
]; ];
@ -219,6 +278,8 @@ in {
# Needs external access for database updates # Needs external access for database updates
"external_network" "external_network"
]; ];
user = mkUserMap "mailserver-antivirus";
volumes = [ "${cfg.state-directory}/antivirus:/state" ];
nixos = { nixos = {
useSystemd = true; useSystemd = true;
configuration = [ configuration = [
@ -228,7 +289,7 @@ in {
system.nssModules = lib.mkForce [ ]; system.nssModules = lib.mkForce [ ];
fudo.mail.clamav = { fudo.mail.clamav = {
enable = true; enable = true;
state-directory = "${cfg.state-directory}/rspamd"; state-directory = "/state";
port = antispamPort; port = antispamPort;
}; };
} }
@ -237,6 +298,8 @@ in {
}; };
dkim = { dkim = {
networks = [ "internal_network" ]; networks = [ "internal_network" ];
user = mkUserMap "mailserver-dkim";
volumes = [ "${cfg.state-directory}/dkim:/state" ];
nixos = { nixos = {
useSystemd = true; useSystemd = true;
configuration = [ configuration = [
@ -250,7 +313,7 @@ in {
domains = [ cfg.primary-domain ] ++ cfg.extra-domains; domains = [ cfg.primary-domain ] ++ cfg.extra-domains;
}; };
port = dkimPort; port = dkimPort;
state-directory = "${cfg.state-directory}/dkim"; state-directory = "/state";
} }
]; ];
}; };

View File

@ -118,6 +118,44 @@ in {
default = 1725; default = 1725;
}; };
}; };
ldap-conf = mkOption {
type = str;
description = "Path to LDAP dovecot2 configuration.";
};
rspamd-server = {
host = mkOption {
type = str;
description = "Hostname of rspamd server.";
};
port = mkOption {
type = port;
description = "Port on which rspamd is running.";
};
};
lmtp-server = {
host = mkOption {
type = str;
description = "Hostname of lmtp server.";
};
port = mkOption {
type = port;
description = "Port on which lmtp is running.";
};
};
dkim-server = {
host = mkOption {
type = str;
description = "Hostname of dkim server.";
};
port = mkOption {
type = port;
description = "Port on which dkim is running.";
};
};
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
@ -140,6 +178,44 @@ in {
port = cfg.metrics-port; port = cfg.metrics-port;
}; };
dovecot2 = {
enable = true;
enablePAM = false;
enableImap = false;
extraConfig = ''
# Extra Config
${lib.optionalString cfg.debug "auth_debug = yes"}
# When looking up usernames, just use the name, not the full address
auth_username_format = %n
auth_mechanisms = login plain
passdb {
driver = ldap
args = ${ldapConfig}
}
userdb = {
driver = ldap
args = ${ldap-conf}
}
service auth {
unix_listener auth {
mode = 0600
user = ${config.services.postfix.user}
group = ${config.services.postfix.group}
}
}
service auth-worker {
user = ${config.services.dovecot2.user}
idle_kill = 3
}
'';
};
postfix = { postfix = {
enable = true; enable = true;
@ -199,6 +275,52 @@ in {
pcreFile = name: "pcre:/var/lib/postfix/conf/${name}"; pcreFile = name: "pcre:/var/lib/postfix/conf/${name}";
mappedFile = name: "hash:/var/lib/postfix/conf/${name}"; mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
sender-restrictions = [
"check_sender_access ${mapped-file "reject_senders"}"
"reject_sender_login_mismatch"
"reject_non_fqdn_sender"
"reject_unknown_sender_domain"
"permit_mynetworks"
"permit_sasl_authenticated"
] ++ (map (blacklist: "reject_rbl_client ${blacklist}")
cfg.blacklist.dns) ++ [ "reject" ];
relay-restrictions = [
"reject_unauth_destination"
"reject_unauth_pipelining"
"reject_unauth_destination"
"reject_unknown_sender_domain"
"permit_mynetworks"
"permit_sasl_authenticated"
] ++ (map (blacklist: "reject_rbl_client ${blacklist}")
cfg.blacklist.dns) ++ [ "reject" ];
recipient-restrictions = [
"check_sender_access ${mapped-file "reject_recipients"}"
"reject_unknown_sender_domain"
"reject_unknown_recipient_domain"
"reject_unauth_pipelining"
"reject_unauth_destination"
"reject_invalid_hostname"
"reject_non_fqdn_hostname"
"reject_non_fqdn_sender"
"reject_non_fqdn_recipient"
"check_policy_service unix:private/policy-spf"
] ++ (map (blacklist: "reject_rbl_client ${blacklist}")
cfg.blacklist.dns)
++ [ "permit_mynetworks" "permit_sasl_authenticated" "reject" ];
client-restrictions =
[ "permit_sasl_authenticated" "permit_mynetworks" "reject" ];
helo-restrictions = [
"permit_mynetworks"
"reject_invalid_hostname"
"reject_non_fqdn_helo_hostname"
"reject_unknown_helo_hostname"
] ++ (map (blacklist: "reject_rbl_client ${blacklist}")
cfg.blacklist.dns) ++ [ "permit" ];
in { in {
virtual_mailbox_domains = allDomains; virtual_mailbox_domains = allDomains;
virtual_mailbox_maps = mappedFile "virtual_mailbox_map"; virtual_mailbox_maps = mappedFile "virtual_mailbox_map";
@ -209,7 +331,8 @@ in {
# virtual_gid_maps = let gid = config.users.groups."${cfg.group}".gid; # virtual_gid_maps = let gid = config.users.groups."${cfg.group}".gid;
# in "static: ${toString gid}"; # in "static: ${toString gid}";
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp"; virtual_transport =
"lmtp:inet:${cfg.lmtp-server.host}:${cfg.lmtp-server.port}";
message_size_limit = toString (cfg.message-size-limit * 1024 * 1024); message_size_limit = toString (cfg.message-size-limit * 1024 * 1024);
@ -226,6 +349,7 @@ in {
smtpd_sasl_path = "/run/dovecot2/auth"; smtpd_sasl_path = "/run/dovecot2/auth";
smtpd_sasl_auth_enable = "yes"; smtpd_sasl_auth_enable = "yes";
smtpd_sasl_local_domain = cfg.sasl-domain; smtpd_sasl_local_domain = cfg.sasl-domain;
smtp_sasl_authenticated_header = "yes";
smtpd_sasl_security_options = "noanonymous"; smtpd_sasl_security_options = "noanonymous";
smtpd_sasl_tls_security_options = "noanonymous"; smtpd_sasl_tls_security_options = "noanonymous";
@ -241,47 +365,24 @@ in {
"i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}"; "i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}";
smtpd_milters = [ smtpd_milters = [
"unix:/run/rspamd/rspamd-milter.sock" "inet:${cfg.rspamd-server.host}:${cfg.rspamd-server.port}"
"unix:/var/run/opendkim/opendkim.sock" "inet:${cfg.dkim.host}:${cfg.dkim.port}"
]; ];
non_smtpd_milters = [ non_smtpd_milters = [
"unix:/run/rspamd/rspamd-milter.sock" "inet:${cfg.rspamd-server.host}:${cfg.rspamd-server.port}"
"unix:/var/run/opendkim/opendkim.sock" "inet:${cfg.dkim.host}:${cfg.dkim.port}"
]; ];
smtpd_relay_restrictions = [ helo_required = true;
"permit_mynetworks"
"permit_sasl_authenticated"
"reject_unauth_destination"
"reject_unauth_pipelining"
"reject_unauth_destination"
"reject_unknown_sender_domain"
];
smtpd_sender_restrictions = [ smtpd_relay_restrictions = relay-restrictions;
"check_sender_access ${mapped-file "reject_senders"}"
"permit_mynetworks"
"permit_sasl_authenticated"
"reject_unknown_sender_domain"
];
smtpd_recipient_restrictions = [ smtpd_sender_restrictions = sender-restrictions;
"check_sender_access ${mapped-file "reject_recipients"}"
"permit_mynetworks"
"permit_sasl_authenticated"
"check_policy_service unix:private/policy-spf"
"reject_unknown_recipient_domain"
"reject_unauth_pipelining"
"reject_unauth_destination"
"reject_invalid_hostname"
"reject_non_fqdn_hostname"
"reject_non_fqdn_sender"
"reject_non_fqdn_recipient"
];
smtpd_helo_restrictions = smtpd_recipient_restrictions = recipient-restrictions;
[ "permit_mynetworks" "reject_invalid_hostname" "permit" ];
smtpd_helo_restrictions = helo-restrictions;
# Handled by submission # Handled by submission
smtpd_tls_security_level = "may"; smtpd_tls_security_level = "may";
@ -328,16 +429,16 @@ in {
smtpd_sasl_path = "/run/dovecot2/auth"; smtpd_sasl_path = "/run/dovecot2/auth";
smtpd_sasl_security_options = "noanonymous"; smtpd_sasl_security_options = "noanonymous";
smtpd_sasl_local_domain = cfg.domain; smtpd_sasl_local_domain = cfg.domain;
smtpd_client_restrictions = "permit_sasl_authenticated,reject"; smtpd_client_restrictions = client-restrictions;
smtpd_sender_restrictions = smtpd_sender_restrictions = sender-restrictions;
"reject_sender_login_mismatch,reject_unknown_sender_domain"; smtpd_recipient_restrictions = recipient-restrictions;
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";
}; };
masterConfig = { masterConfig = {
"policy-spf" = let # See: http://www.postfix.org/smtp.8.html
lmtp.args = [ "flags=DO" ];
policy-spf = let
policySpfFile = pkgs.writeText "policyd-spf.conf" policySpfFile = pkgs.writeText "policyd-spf.conf"
(cfg.postfix.policy-spf.extraConfig (cfg.postfix.policy-spf.extraConfig
+ (lib.optionalString cfg.debug "debugLevel = 4")); + (lib.optionalString cfg.debug "debugLevel = 4"));
@ -352,7 +453,7 @@ in {
"${policydSpf}" "${policydSpf}"
]; ];
}; };
"submission-header-cleanup" = let submission-header-cleanup = let
submissionHeaderCleanupRules = submissionHeaderCleanupRules =
pkgs.writeText "submission_header_cleanup_rules" '' pkgs.writeText "submission_header_cleanup_rules" ''
# Removes sensitive headers from mails handed in via the submission port. # Removes sensitive headers from mails handed in via the submission port.
@ -364,6 +465,7 @@ in {
/^X-Mailer:/ IGNORE /^X-Mailer:/ IGNORE
/^User-Agent:/ IGNORE /^User-Agent:/ IGNORE
/^X-Enigmail:/ IGNORE /^X-Enigmail:/ IGNORE
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.hostname}>
''; '';
in { in {
type = "unix"; type = "unix";

View File

@ -62,6 +62,12 @@ in {
scan_mime_parts = false; # scan mail as a whole unit, not parts. seems to be needed to work at all scan_mime_parts = false; # scan mail as a whole unit, not parts. seems to be needed to work at all
} }
''; '';
"rbl.conf".text = ''
rbls {
an_rbl
}
'';
}; };
overrides."milter_headers.conf".text = "extended_spam_headers = true;"; overrides."milter_headers.conf".text = "extended_spam_headers = true;";