Some checks failed
Test / tests (push) Failing after 3m0s
In order to simplify configuration and reduce configuration variability, this commit removes the option to directly add aliases to each single mail account. Instead, aliaes should be centrally configured using the option 'virtualAccountAliases'.
219 lines
7.9 KiB
Nix
219 lines
7.9 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
with (import ./common.nix {inherit config;}); let
|
|
cfg = config.mailsystem;
|
|
|
|
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
|
|
|
attrsToLookupTable = aliases: let
|
|
lookupTables = lib.mapAttrsToList (from: to: {"${from}" = to;}) aliases;
|
|
in
|
|
mergeLookupTables lookupTables;
|
|
|
|
lookupTableToString = attrs: let
|
|
valueToString = value: lib.concatStringsSep ", " value;
|
|
in
|
|
lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs);
|
|
|
|
mergeLookupTables = tables: lib.zipAttrsWith (n: v: lib.flatten v) tables;
|
|
|
|
virtual_accounts = mergeLookupTables (lib.map (name: {"${name}" = name;}) (lib.attrNames cfg.accounts));
|
|
virtual_account_aliases = attrsToLookupTable cfg.virtualAccountAliases;
|
|
|
|
virtual_domain_aliases = let
|
|
alias_domains = lib.mapAttrs' (src: dst: lib.nameValuePair "@${src}" "@${dst}") cfg.virtualDomainAliases;
|
|
in
|
|
attrsToLookupTable alias_domains;
|
|
|
|
all_virtual_aliases = mergeLookupTables [virtual_accounts virtual_account_aliases virtual_domain_aliases];
|
|
|
|
aliases_file = let
|
|
content = lookupTableToString all_virtual_aliases;
|
|
in
|
|
builtins.toFile "virtual_aliases" content;
|
|
|
|
# File containing all mappings of authenticated accounts and their sender mail addresses.
|
|
virtual_accounts_file = let
|
|
content = lookupTableToString all_virtual_aliases;
|
|
in
|
|
builtins.toFile "virtual_accounts" content;
|
|
|
|
virtual_domains_file = builtins.toFile "virtual_domains" (lib.concatStringsSep "\n" cfg.domains);
|
|
|
|
denied_recipients = map (account: "${account.name} REJECT ${account.rejectMessage}") (lib.filter (account: account.isSystemUser) (lib.attrValues cfg.accounts));
|
|
denied_recipients_file = builtins.toFile "denied_recipients" (lib.concatStringsSep "\n" denied_recipients);
|
|
|
|
submission_header_cleanup_rules = pkgs.writeText "submission_header_cleanup_rules" ''
|
|
# Removes sensitive headers from mails handed in via the submission port.
|
|
# See https://thomas-leister.de/mailserver-debian-stretch/
|
|
# Uses "pcre" style regex.
|
|
|
|
/^Received:/ IGNORE
|
|
/^X-Originating-IP:/ IGNORE
|
|
/^X-Mailer:/ IGNORE
|
|
/^User-Agent:/ IGNORE
|
|
/^X-Enigmail:/ IGNORE
|
|
|
|
# Replace the user's submitted hostname with the server's FQDN to hide the
|
|
# user's host/network.
|
|
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.fqdn}>
|
|
'';
|
|
|
|
tls_protocols = "TLSv1.3, TLSv1.2, !TLSv1.1, !TLSv1, !SSLv2, !SSLv3";
|
|
tls_exclude_ciphers = "MD5, DES, ADH, RC4, PSD, SRP, 3DES, eNULL, aNULL";
|
|
in {
|
|
config = lib.mkIf cfg.enable {
|
|
assertions =
|
|
lib.mapAttrsToList (
|
|
src: dst: {
|
|
assertion = (builtins.elem src cfg.domains) && (builtins.elem dst cfg.domains);
|
|
message = "Both aliased domain (${src}) and actual domain (${dst}) need to be managed by the mailserver.";
|
|
}
|
|
)
|
|
cfg.virtualDomainAliases;
|
|
|
|
services.postfix = {
|
|
enable = true;
|
|
hostname = "${cfg.reverseFqdn}";
|
|
networksStyle = "host";
|
|
|
|
sslCert = sslCertPath;
|
|
sslKey = sslKeyPath;
|
|
|
|
enableSubmissions = true;
|
|
|
|
# TODO: create function to simplify this?
|
|
mapFiles."virtual_aliases" = aliases_file;
|
|
mapFiles."virtual_accounts" = virtual_accounts_file;
|
|
mapFiles."denied_recipients" = denied_recipients_file;
|
|
virtual = lookupTableToString all_virtual_aliases;
|
|
|
|
submissionsOptions = {
|
|
smtpd_tls_security_level = "encrypt";
|
|
smtpd_sasl_auth_enable = "yes";
|
|
smtpd_sasl_type = "dovecot";
|
|
smtpd_sasl_path = "/run/dovecot2/auth";
|
|
smtpd_sasl_security_options = "noanonymous";
|
|
smtpd_sasl_local_domain = "$myhostname";
|
|
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
|
|
# use mappedFile -> different path?
|
|
smtpd_sender_login_maps = "hash:/etc/postfix/virtual_accounts";
|
|
smtpd_sender_restrictions = "reject_sender_login_mismatch";
|
|
smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
|
|
cleanup_service_name = "submission-header-cleanup";
|
|
};
|
|
|
|
config = {
|
|
mydestination = "";
|
|
recipient_delimiter = "+";
|
|
smtpd_banner = "${cfg.fqdn} ESMTP NO UCE";
|
|
disable_vrfy_command = true;
|
|
message_size_limit = toString cfg.messageSizeLimit;
|
|
|
|
virtual_uid_maps = "static:${toString cfg.vmailUID}";
|
|
virtual_gid_maps = "static:${toString cfg.vmailUID}";
|
|
virtual_mailbox_base = cfg.mailDirectory;
|
|
virtual_mailbox_domains = virtual_domains_file;
|
|
virtual_mailbox_maps = [
|
|
(mappedFile "virtual_aliases")
|
|
];
|
|
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
|
|
# Avoid leakage of X-Original-To, X-Delivered-To headers between recipients
|
|
lmtp_destination_recipient_limit = "1";
|
|
|
|
# sasl with dovecot (enforce authentication via dovecot)
|
|
smtpd_sasl_type = "dovecot";
|
|
smtpd_sasl_path = "/run/dovecot2/auth";
|
|
smtpd_sasl_auth_enable = true;
|
|
smtpd_relay_restrictions = [
|
|
"permit_mynetworks"
|
|
"permit_sasl_authenticated"
|
|
"reject_unauth_destination"
|
|
];
|
|
smtpd_recipient_restrictions = [
|
|
"check_recipient_access ${mappedFile "denied_recipients"}"
|
|
];
|
|
|
|
# TLS settings, inspired by https://github.com/jeaye/nix-files
|
|
# Submission by mail clients is handled in submissionOptions
|
|
smtpd_tls_security_level = "may";
|
|
|
|
# Disable obsolete protocols
|
|
smtpd_tls_protocols = tls_protocols;
|
|
smtp_tls_protocols = tls_protocols;
|
|
smtpd_tls_mandatory_protocols = tls_protocols;
|
|
smtp_tls_mandatory_protocols = tls_protocols;
|
|
|
|
smtp_tls_ciphers = "high";
|
|
smtpd_tls_ciphers = "high";
|
|
smtp_tls_mandatory_ciphers = "high";
|
|
smtpd_tls_mandatory_ciphers = "high";
|
|
|
|
# Disable deprecated ciphers
|
|
smtpd_tls_mandatory_exclude_ciphers = tls_exclude_ciphers;
|
|
smtpd_tls_exclude_ciphers = tls_exclude_ciphers;
|
|
smtp_tls_mandatory_exclude_ciphers = tls_exclude_ciphers;
|
|
smtp_tls_exclude_ciphers = tls_exclude_ciphers;
|
|
|
|
tls_preempt_cipherlist = true;
|
|
|
|
# Allowing AUTH on a non-encrypted connection poses a security risk
|
|
smtpd_tls_auth_only = true;
|
|
# Log only a summary message on TLS handshake completion
|
|
smtpd_tls_loglevel = "1";
|
|
|
|
# Configure a non-blocking source of randomness
|
|
tls_random_source = "dev:/dev/urandom";
|
|
|
|
smtpd_milters = [
|
|
"unix:${rspamdProxySocket}"
|
|
];
|
|
# Also use milter for outgoing mails (for e.g., dkim)
|
|
non_smtpd_milters = [
|
|
"unix:${rspamdProxySocket}"
|
|
];
|
|
milter_protocol = "6";
|
|
milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}";
|
|
|
|
# Fix for https://www.postfix.org/smtp-smuggling.html
|
|
smtpd_forbid_bare_newline = "yes";
|
|
smtpd_forbid_bare_newline_exclusions = "$mynetworks";
|
|
};
|
|
|
|
masterConfig = {
|
|
"lmtp" = {
|
|
# Add headers when delivering, see http://www.postfix.org/smtp.8.html
|
|
# D => Delivered-To, O => X-Original-To, R => Return-Path
|
|
args = ["flags=O"];
|
|
};
|
|
"submission-header-cleanup" = {
|
|
type = "unix";
|
|
private = false;
|
|
chroot = false;
|
|
maxproc = 0;
|
|
command = "cleanup";
|
|
args = ["-o" "header_checks=pcre:${submission_header_cleanup_rules}"];
|
|
};
|
|
};
|
|
};
|
|
|
|
systemd.services.postfix = {
|
|
wants = sslCertService;
|
|
after =
|
|
["dovecot2.service" "rspamd.service"]
|
|
++ sslCertService;
|
|
requires = ["dovecot2.service" "rspamd.service"];
|
|
};
|
|
|
|
networking.firewall = lib.mkIf cfg.openFirewall {
|
|
allowedTCPPorts = [
|
|
25 # smtp
|
|
465 # submissions
|
|
];
|
|
};
|
|
};
|
|
}
|