{extraModules}: { config, lib, nodes, pkgs, ... }: let keyAlgoToKeyType = keyAlgo: if keyAlgo == "ed25519-sha256" then "ed25519" else if keyAlgo == "rsa-sha-256" || keyAlgo == "rsa-sha-1" then "rsa" else builtins.throw "Impossible"; mkZone = user: nodes: lib: cfg: { SOA = { nameServer = "ns.server.com"; adminEmail = "${user}@${user}.com"; serial = 2024012301; }; MX = [ { preference = 10; exchange = "${cfg.fqdn}."; } ]; # https://www.rfc-editor.org/rfc/rfc8461.html#section-3.1 # Also see the policy in the hmtl part. MTA-STS = [ { id = "20250228Z"; } ]; # https://www.rfc-editor.org/rfc/rfc7208.html # https://en.wikipedia.org/wiki/Sender_Policy_Framework TXT = [ (builtins.concatStringsSep " " [ "v=spf1" # The version. "+mx" # Allow mail from this domain MX record. "-all" # Reject all other emails if the previous mechanism did not match. ]) ]; # https://www.rfc-editor.org/rfc/rfc6376.html#section-3.6.1 # https://www.rfc-editor.org/rfc/rfc6376.html#section-7.5 DKIM = [ { selector = "mail"; k = keyAlgoToKeyType cfg.security.dkimKeys."${user}.com".keyAlgorithm; p = cfg.security.dkimKeys."${user}.com".dkimPublicKey; s = ["email"]; t = ["s"]; } ]; # https://www.rfc-editor.org/rfc/rfc7489.html#section-6.3 DMARC = [ { adkim = "strict"; aspf = "strict"; fo = ["0" "1" "d" "s"]; p = "quarantine"; rua = cfg.admin; ruf = [cfg.admin]; } ]; A = [ nodes.${user}.networking.primaryIPAddress ]; AAAA = [ nodes.${user}.networking.primaryIPv6Address ]; }; mkServerZone = serverName: nodes: lib: let cfg = nodes."${serverName}_server".vhack.stalwart-mail; in { SOA = { nameServer = "ns.server.com"; adminEmail = "admin@server.com"; serial = 2024012301; }; MX = [ { preference = 10; exchange = "${serverName}.server.com."; } ]; # https://www.rfc-editor.org/rfc/rfc6376.html#section-3.6.1 # https://www.rfc-editor.org/rfc/rfc6376.html#section-7.5 DKIM = [ { selector = "mail"; k = keyAlgoToKeyType cfg.security.dkimKeys."${serverName}.server.com".keyAlgorithm; p = cfg.security.dkimKeys."${serverName}.server.com".dkimPublicKey; s = ["email"]; t = ["s"]; } ]; # https://www.rfc-editor.org/rfc/rfc7489.html#section-6.3 DMARC = [ { adkim = "strict"; aspf = "strict"; fo = ["0" "1" "d" "s"]; p = "quarantine"; rua = cfg.admin; ruf = [cfg.admin]; } ]; # https://www.rfc-editor.org/rfc/rfc7208.html # NOTE(@bpeetz): This server might not be directly sending mail, but it is still required for # the SMTP EHLO check. <2025-02-25> TXT = [ (builtins.concatStringsSep " " [ "v=spf1" # The version. "+mx" # Allow mail from this domain MX record. "-all" # Reject all other emails if the previous mechanism did not match. ]) ]; A = [ nodes."${serverName}_server".networking.primaryIPAddress ]; AAAA = [ nodes."${serverName}_server".networking.primaryIPv6Address ]; }; in { imports = extraModules ++ [ ../../../../../modules ./acme/client.nix ]; networking.nameservers = lib.mkForce [ nodes.name_server.networking.primaryIPAddress nodes.name_server.networking.primaryIPv6Address ]; services.nginx = { logError = "stderr debug"; virtualHosts = let mkStsHost = mx: { forceSSL = true; enableACME = true; root = pkgs.runCommandLocal "mkPolicy" {} '' mkdir --parents $out/.well-known/ # https://www.rfc-editor.org/rfc/rfc8461.html#section-3.2 cat << EOF > $out/.well-known/mta-sts.txt version: STSv1 mode: enforce mx: ${mx} max_age: 604800 EOF ''; }; in { "mta-sts.alice.com" = mkStsHost "mail2.server.com"; "mta-sts.bob.com" = mkStsHost "mail1.server.com"; }; }; vhack = { nginx = { enable = true; }; dns = { enable = true; openFirewall = true; interfaces = [ nodes.name_server.networking.primaryIPAddress nodes.name_server.networking.primaryIPv6Address ]; zones = let stsZone = { SOA = { nameServer = "ns"; adminEmail = "admin@server.com"; serial = 2025012301; }; useOrigin = false; A = [ nodes.name_server.networking.primaryIPAddress ]; AAAA = [ nodes.name_server.networking.primaryIPv6Address ]; }; in { "arpa" = { SOA = { nameServer = "ns"; adminEmail = "admin@server.com"; serial = 2025012301; }; useOrigin = false; PTR = [ { name = "acme.test"; ip.v4 = nodes.acme.networking.primaryIPAddress; } { name = "acme.test"; ip.v6 = nodes.acme.networking.primaryIPv6Address; } { name = "alice.com"; ip.v4 = nodes.alice.networking.primaryIPAddress; } { name = "alice.com"; ip.v6 = nodes.alice.networking.primaryIPv6Address; } { name = "bob"; ip.v4 = nodes.bob.networking.primaryIPAddress; } { name = "bob"; ip.v6 = nodes.bob.networking.primaryIPv6Address; } { name = "mail1.server.com"; ip.v4 = nodes.mail1_server.networking.primaryIPAddress; } { name = "mail1.server.com"; ip.v6 = nodes.mail1_server.networking.primaryIPv6Address; } { name = "mail2.server.com"; ip.v4 = nodes.mail2_server.networking.primaryIPAddress; } { name = "mail2.server.com"; ip.v6 = nodes.mail2_server.networking.primaryIPv6Address; } { name = "ns.server.com"; ip.v4 = nodes.name_server.networking.primaryIPAddress; } { name = "ns.server.com"; ip.v6 = nodes.name_server.networking.primaryIPv6Address; } ]; }; "alice.com" = mkZone "alice" nodes lib nodes.mail2_server.vhack.stalwart-mail; "mta-sts.alice.com" = stsZone; "bob.com" = mkZone "bob" nodes lib nodes.mail1_server.vhack.stalwart-mail; "mta-sts.bob.com" = stsZone; "mail1.server.com" = mkServerZone "mail1" nodes lib; "mail2.server.com" = mkServerZone "mail2" nodes lib; "ns.server.com" = { SOA = { nameServer = "ns"; adminEmail = "admin@server.com"; serial = 2025012301; }; useOrigin = false; A = [ nodes.name_server.networking.primaryIPAddress ]; AAAA = [ nodes.name_server.networking.primaryIPv6Address ]; }; "acme.test" = { SOA = { nameServer = "ns"; adminEmail = "admin@server.com"; serial = 2025012301; }; useOrigin = false; A = [ nodes.acme.networking.primaryIPAddress ]; AAAA = [ nodes.acme.networking.primaryIPv6Address ]; }; "server.com" = { SOA = { nameServer = "ns"; adminEmail = "admin@server.com"; serial = 2025012301; }; useOrigin = false; NS = [ "ns.server.com." ]; }; }; }; }; }