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 5a6978d..ccb8826 100644 Binary files a/secrets/global.nix.age and b/secrets/global.nix.age differ