mailnix/mailsystem/postfix.nix
Thomas Preisner faf6f549b0
Some checks failed
Test / tests (push) Failing after 3m0s
Remove accounts.<name>.aliases and rename extraVirtualAliases to virtualAccountAliases
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'.
2025-01-06 23:29:40 +01:00

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
];
};
};
}