summaryrefslogtreecommitdiff
path: root/sys/mta
diff options
context:
space:
mode:
Diffstat (limited to 'sys/mta')
-rw-r--r--sys/mta/default.nix269
1 files changed, 269 insertions, 0 deletions
diff --git a/sys/mta/default.nix b/sys/mta/default.nix
new file mode 100644
index 0000000..57c1c27
--- /dev/null
+++ b/sys/mta/default.nix
@@ -0,0 +1,269 @@
+{
+ config,
+ lib,
+ pkgs,
+ ...
+}:
+with lib; let
+ cfg = config.local.mta;
+
+ inherit (config.local) domains virtual users;
+ inherit (config.networking) domain;
+
+ isBackup = cfg.mode == "backup";
+ isPrimary = cfg.mode == "primary";
+
+ allDomains = optional (! virtualDomains ? ${domain}) domain ++ attrNames virtualDomains;
+ virtualDomains = filterAttrs (name: _: name != domain) virtual;
+
+ cert = config.security.acme.certs.${mtaDomain.main}.directory;
+
+ mtaDomain =
+ if isPrimary
+ then domains.smtp
+ else domains.smtp-backup;
+
+ mdaTransport =
+ if isPrimary
+ then "lmtp:inet:${cfg.mdaAddr}:${toString cfg.lmtpPort}"
+ else "error:bad transport";
+in {
+ options.local.mta = {
+ enable = mkEnableOption "mail transfer agent";
+
+ mode = mkOption {
+ type = types.enum ["primary" "backup"];
+ };
+
+ mdaAddr = mkOption {
+ type = types.str;
+ };
+
+ saslPort = mkOption {
+ type = types.port;
+ };
+
+ lmtpPort = mkOption {
+ type = types.port;
+ };
+
+ relayListen = mkOption {
+ type = types.str;
+ };
+ };
+
+ config = mkIf cfg.enable {
+ services = {
+ fail2ban.jails.postfix.settings = {
+ filter = "postfix[mode=aggressive]";
+ };
+
+ opendkim = mkIf isPrimary {
+ enable = true;
+
+ group = "postfix";
+ domains = "csl:" + concatStringsSep "," ([domain] ++ attrNames virtualDomains);
+ selector = "202408";
+
+ configFile = pkgs.writeText "opendkim.conf" ''
+ UMask 007
+ InternalHosts 0.0.0.0/0,::/0
+ '';
+ };
+
+ postfix = {
+ enable = true;
+ enableSmtp = true;
+ enableSubmissions = isPrimary;
+
+ inherit domain;
+ hostname = mtaDomain.main;
+
+ #TODO: check_recipient_access para rechazar localhost desde afuera
+ destination = optionals isPrimary ["localhost" "$mydomain"];
+ origin = "$mydomain";
+
+ networksStyle = "host";
+
+ relayHost = optionalString isBackup domains.smtp.main;
+ lookupMX = false;
+
+ relayDomains =
+ if isBackup
+ then allDomains
+ else null;
+
+ sslKey = "${cert}/key.pem";
+ sslCert = "${cert}/fullchain.pem";
+
+ # También es postmaster
+ rootAlias = config.local.sysadmin;
+
+ extraAliases =
+ optionalString isPrimary
+ (concatLines (flatten (mapAttrsToList
+ (name: user:
+ map
+ (alias: "${alias}: ${name}")
+ user.hardAliases)
+ users)));
+
+ localRecipients =
+ optionals isPrimary
+ (map (user: "${user}@${domain}")
+ (attrNames (users // virtual.${domain}.users)));
+
+ virtual =
+ optionalString isPrimary
+ (concatLines (flatten (mapAttrsToList
+ (name: virtual:
+ mapAttrsToList
+ (alias: targets: "${alias}@${name} ${concatStringsSep ", " targets}")
+ virtual.aliases)
+ virtual)));
+
+ mapFiles = optionalAttrs isPrimary {
+ sender_ccerts =
+ pkgs.writeText "postfix-sender_ccerts"
+ (concatLines (flatten (mapAttrsToList
+ (username: user:
+ map
+ (alias: "${alias}@${domain} CCERTS ${concatStringsSep ","
+ (map (certPath: config.local.pki.byPath.${certPath}.fingerprint.sha256-bytes-upper)
+ user.mail.certs)}")
+ ([username] ++ user.hardAliases))
+ (filterAttrs (_: user: user.mail.certs != []) users))));
+
+ sender_login =
+ pkgs.writeText "postfix-sender_login"
+ (concatLines (flatten (mapAttrsToList
+ (username: user:
+ map
+ (alias: "${alias}@${domain} ${username}")
+ ([username] ++ user.hardAliases))
+ users)));
+
+ virtual_recipients =
+ pkgs.writeText "postfix-virtual_recipients"
+ (concatLines (flatten (mapAttrsToList
+ (virtualDomain: virtual:
+ mapAttrsToList
+ # El lado derecho de esta tabla debe existir pero nunca se usa
+ (username: _: "${username}@${virtualDomain} foo")
+ virtual.users)
+ virtualDomains)));
+
+ virtual_rules =
+ pkgs.writeText "postfix-virtual_rules"
+ (concatLines (flatten (mapAttrsToList
+ (name: virtual:
+ map
+ (rule: "/^${rule.pattern}@${name}$/ ${concatStringsSep ", " rule.targets}")
+ virtual.rules)
+ virtual)));
+ };
+
+ config =
+ {
+ # user+extension@domain.tld
+ recipient_delimiter = optionalString isPrimary "+";
+
+ message_size_limit = toString (50 * 1048576);
+
+ local_transport = mdaTransport;
+ virtual_transport = mdaTransport;
+
+ smtpd_tls_auth_only = true;
+ # Nota: smtpd_tls_dh1024_param_file fue deprecado en 3.9
+
+ tls_append_default_CA = false; # Crítico
+
+ # https://linux-audit.com/postfix-hardening-guide-for-security-and-privacy/
+ smtpd_helo_required = true;
+ disable_vrfy_command = true;
+ }
+ // optionalAttrs isPrimary {
+ virtual_alias_maps = mkAfter ["pcre:/etc/postfix/virtual_rules"];
+ virtual_mailbox_domains = attrNames virtualDomains;
+ virtual_mailbox_maps = ["hash:/etc/postfix/virtual_recipients"];
+
+ smtpd_sasl_type = "dovecot";
+ smtpd_sasl_path = "inet:${cfg.mdaAddr}:${toString cfg.saslPort}";
+ smtpd_sasl_local_domain = "$mydomain";
+ smtpd_sasl_security_options = ["noanonymous"];
+
+ smtpd_tls_CAfile = "${config.local.pki.ca.mail.fullchain}";
+ smtpd_tls_ccert_verifydepth = "1";
+
+ # Inventado, no es parámetro de postfix
+ local_submission_client_restrictions = [
+ "permit_tls_all_clientcerts"
+ "permit_sasl_authenticated"
+ "reject"
+ ];
+
+ smtpd_sender_login_maps = ["hash:/etc/postfix/sender_login"];
+
+ smtpd_relay_restrictions = [
+ "permit_mynetworks"
+ "permit_tls_all_clientcerts"
+ "permit_sasl_authenticated"
+ "reject_unauth_destination"
+ ];
+
+ smtpd_sender_restrictions = [
+ "check_sender_access hash:/etc/postfix/sender_ccerts"
+ "reject_sender_login_mismatch"
+ ];
+
+ smtpd_milters = "unix:/run/opendkim/opendkim.sock";
+ non_smtpd_milters = "$smtpd_milters";
+ milter_default_action = "accept";
+ }
+ // optionalAttrs isBackup {
+ inet_interfaces = [cfg.relayListen];
+
+ smtpd_relay_restrictions = [
+ "reject_unauth_destination"
+ ];
+ };
+
+ # Importante: existe submissionOptions por aparte, no son iguales
+ submissionsOptions = optionalAttrs isPrimary {
+ smtpd_client_restrictions = "$local_submission_client_restrictions";
+ smtpd_sasl_auth_enable = "yes";
+ smtpd_tls_ask_ccert = "yes";
+ smtpd_tls_security_level = "encrypt";
+ };
+ };
+ };
+
+ #TODO: solo para las destination addresses necesarias
+ networking.firewall.allowedTCPPorts = optionals isPrimary [25 465];
+
+ local = {
+ boot.impermanence.directories =
+ [
+ {
+ directory = "/var/lib/postfix";
+ user = "root";
+ group = "root";
+ mode = "u=rwx,g=rx,o=rx";
+ }
+ ]
+ ++ optionals isPrimary [
+ {
+ directory = "/var/lib/opendkim";
+ user = "opendkim";
+ group = "postfix";
+ mode = "u=rwx,g=,o=";
+ }
+ ];
+
+ certs.smtp.enable = isPrimary;
+ certs.smtp-backup.enable = isBackup;
+ };
+
+ security.acme.certs.${mtaDomain.main}.reloadServices = ["postfix.service"];
+ };
+}