From ee82d91214669d09bd6384f18e7c0e878ad73ae3 Mon Sep 17 00:00:00 2001 From: oddlama Date: Thu, 11 Apr 2024 17:08:40 +0200 Subject: [PATCH] feat: add preliminary maddy configuration --- flake.lock | 6 +- hosts/envoy/default.nix | 1 + hosts/envoy/maddy.nix | 213 ++++++++++++++++++++++++++++++++++++++++ secrets/global.nix.age | Bin 2476 -> 2466 bytes 4 files changed, 217 insertions(+), 3 deletions(-) create mode 100644 hosts/envoy/maddy.nix diff --git a/flake.lock b/flake.lock index 094159c..943fb0a 100644 --- a/flake.lock +++ b/flake.lock @@ -1053,11 +1053,11 @@ "pre-commit-hooks": "pre-commit-hooks_4" }, "locked": { - "lastModified": 1710447185, - "narHash": "sha256-M63b7f5dnGtLAZmgzSepQvcVA++QRJ+h8fSlyowgYcI=", + "lastModified": 1712774101, + "narHash": "sha256-t58qLvRLjrekfnHWS5Un5LXQJCrLeycKcuPRtoVqJbw=", "owner": "oddlama", "repo": "nixos-extra-modules", - "rev": "a4f79d7479bf63fb99e1d19cb6502feabc2854c3", + "rev": "0f4e5f7391532ddf105020a5be75421ea2e4fdc7", "type": "github" }, "original": { diff --git a/hosts/envoy/default.nix b/hosts/envoy/default.nix index 7aab654..0f929b2 100644 --- a/hosts/envoy/default.nix +++ b/hosts/envoy/default.nix @@ -9,6 +9,7 @@ ./acme.nix ./fs.nix ./net.nix + ./maddy.nix ]; boot.mode = "bios"; diff --git a/hosts/envoy/maddy.nix b/hosts/envoy/maddy.nix new file mode 100644 index 0000000..f9ffa9b --- /dev/null +++ b/hosts/envoy/maddy.nix @@ -0,0 +1,213 @@ +{ + config, + pkgs, + lib, + ... +}: let + mailDomains = config.repo.secrets.global.domains.mail; + primaryDomain = mailDomains.primary; + maddyBackupDir = "/var/cache/backups/maddy"; +in { + systemd.tmpfiles.settings."10-maddy".${maddyBackupDir}.d = { + inherit (config.services.maddy) user group; + mode = "0770"; + }; + + environment.persistence."/persist".directories = [ + { + directory = "/var/lib/maddy"; + inherit (config.services.maddy) user group; + mode = "0750"; + } + ]; + + # For each mail domain, add MTA STS entry via nginx + # FIXME: autoconfig + services.nginx.virtualHosts = lib.genAttrs (map (x: "mta-sts.${x}") mailDomains.all) (_x: { + forceSSL = true; + useACMEWildcardHost = true; + locations."=/.well-known/mta-sts.txt".alias = pkgs.writeText "mta-sts.txt" '' + version: STSv1 + mode: enforce + mx: mx1.${primaryDomain} + max_age: 86400 + ''; + }); + + networking.firewall.allowedTCPPorts = [25 465 993]; + services.maddy = { + enable = true; + hostname = "mx1.${primaryDomain}"; + inherit primaryDomain; + localDomains = mailDomains.all; + tls = { + loader = "file"; + certificates = [ + { + keyPath = "${config.security.acme.certs.${primaryDomain}.directory}/key.pem"; + certPath = "${config.security.acme.certs.${primaryDomain}.directory}/fullchain.pem"; + } + ]; + }; + #ensureCredentials = { + # "me@${primaryDomain}".passwordFile = config.age.secrets.patrickPasswd.path; + #}; + #ensureAccounts = [ + # "me@${primaryDomain}" + #]; + config = + /* + bash + */ + '' + auth.pass_table local_authdb { + table sql_table { + driver sqlite3 + dsn mailboxes.db + table_name users + } + } + + storage.imapsql local_mailboxes { + driver sqlite3 + dsn imap.db + } + + table.chain local_rewrites { + optional_step sql_query { + driver sqlite + dsn mailboxes.db + lookup "SELECT alias FROM aliases WHERE address = $1" + } + } + + msgpipeline local_routing { + destination $(local_domains) { + modify { + replace_rcpt &local_rewrites + } + + deliver_to &local_mailboxes + } + + default_destination { + reject 550 5.1.1 "User doesn't exist" + } + } + + # ---------------------------------------------------------------------------- + # Endpoints + + imap tls://0.0.0.0:993 { + auth &local_authdb + storage &local_mailboxes + } + + # SMTP for incoming mails from other servers. + # Allows anyone to connect and give us local mail, but doesn't allow message relaying. + smtp tcp://0.0.0.0:25 { + limits { + # Up to 20 msgs/sec across max. 10 SMTP connections. + all rate 20 1s + all concurrency 10 + } + + dmarc yes + max_message_size 256M + check { + require_mx_record + dkim + spf + } + + source $(local_domains) { + reject 501 5.1.8 "Use Submission for outgoing SMTP" + } + default_source { + destination $(local_domains) { + deliver_to &local_routing + } + default_destination { + reject 550 5.1.1 "User doesn't exist" + } + } + } + + # Submission with implicit TLS for authenticated users. + # Allows only authenticated users, and allows message relaying. + # Contrary to popular belief, port 465 is [NOT deprecated](https://datatracker.ietf.org/doc/html/rfc8314#section-7.3), + # and has been assigned for "Message Submission over TLS protocol", + # I neither need nor want to use STARTTLS. + submission tls://0.0.0.0:465 { + limits { + # Up to 50 msgs/sec across any amount of SMTP connections. + all rate 50 1s + } + + auth &local_authdb + + source $(local_domains) { + check { + authorize_sender { + prepare_email &local_rewrites + user_to_email identity + } + } + + destination $(local_domains) { + modify { + dkim $(local_domains) default + } + deliver_to &local_routing + } + default_destination { + modify { + dkim $(local_domains) default + } + deliver_to &remote_queue + } + } + default_source { + reject 501 5.1.8 "Non-local sender domain" + } + } + + # ---------------------------------------------------------------------------- + # Outbound configuration + + target.queue remote_queue { + target &outbound_delivery + + autogenerated_msg_domain $(primary_domain) + bounce { + destination $(local_domains) { + deliver_to &local_routing + } + default_destination { + reject 550 5.0.0 "Refusing to send DSNs to non-local addresses" + } + } + } + + target.remote outbound_delivery { + limits { + # Up to 20 msgs/sec across max. 10 SMTP connections + # for each recipient domain. + destination rate 20 1s + destination concurrency 10 + } + mx_auth { + dane + mtasts { + cache fs + fs_dir mtasts_cache/ + } + local_policy { + min_tls_level encrypted + min_mx_level none + } + } + } + ''; + }; +} diff --git a/secrets/global.nix.age b/secrets/global.nix.age index 5a6978df788fd4390691327fd03c5b72f754c48d..ccb88262d55217b18f471bc85133906b2d2fe16f 100644 GIT binary patch delta 2460 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