From 4177868f1c3b173dde3e2ca64f45d33ab3225950 Mon Sep 17 00:00:00 2001 From: Lu Wang Date: Thu, 19 Dec 2024 21:26:19 +0800 Subject: [PATCH] services/mail: init postfix, dovecot and rspamd --- nixos/hosts/suwako-vie0/default.nix | 3 + nixos/modules/networking/ports.nix | 6 + nixos/profiles/services/mail/dovecot.nix | 171 +++++++++++++++++++++++ nixos/profiles/services/mail/postfix.nix | 84 +++++++++++ nixos/profiles/services/mail/rspamd.nix | 60 ++++++++ secrets/hosts/suwako-vie0.yaml | 7 +- zones/common.nix | 19 +++ zones/rebmit.link.nix | 59 +++++--- zones/rebmit.moe.nix | 4 + 9 files changed, 391 insertions(+), 22 deletions(-) create mode 100644 nixos/profiles/services/mail/dovecot.nix create mode 100644 nixos/profiles/services/mail/postfix.nix create mode 100644 nixos/profiles/services/mail/rspamd.nix diff --git a/nixos/hosts/suwako-vie0/default.nix b/nixos/hosts/suwako-vie0/default.nix index a15541f..1d3a2d5 100644 --- a/nixos/hosts/suwako-vie0/default.nix +++ b/nixos/hosts/suwako-vie0/default.nix @@ -11,6 +11,9 @@ services.caddy services.keycloak services.knot.secondary + services.mail.dovecot + services.mail.postfix + services.mail.rspamd services.matrix.heisenbridge services.matrix.mautrix-telegram services.matrix.synapse diff --git a/nixos/modules/networking/ports.nix b/nixos/modules/networking/ports.nix index 9ed260e..5c3daaf 100644 --- a/nixos/modules/networking/ports.nix +++ b/nixos/modules/networking/ports.nix @@ -9,8 +9,12 @@ in type = with types; attrsOf port; default = { # standard ports + smtp = 25; http = 80; https = 443; + smtp-tls = 465; + smtp-starttls = 587; + imap-tls = 993; socks = 1080; ssh = 2222; @@ -22,6 +26,8 @@ in matrix-synapse = 4030; heisenbridge = 4031; mautrix-telegram = 4032; + rspamd-controller = 4040; + rspamd-redis = 4041; # public ports enthalpy-ipsec = 13000; diff --git a/nixos/profiles/services/mail/dovecot.nix b/nixos/profiles/services/mail/dovecot.nix new file mode 100644 index 0000000..4ec3a29 --- /dev/null +++ b/nixos/profiles/services/mail/dovecot.nix @@ -0,0 +1,171 @@ +# Portions of this file are sourced from +# https://github.com/NickCao/flakes/blob/3b03efb676ea602575c916b2b8bc9d9cd13b0d85/nixos/hcloud/iad0/postfix.nix +{ + config, + lib, + pkgs, + ... +}: +let + cfg = config.services.dovecot2; + maildir = "/var/lib/mail"; +in +{ + sops.secrets."mail/dovecot-passdb" = { + sopsFile = config.sops.secretFiles.host; + owner = cfg.user; + }; + + services.postfix = { + config = { + virtual_transport = "lmtp:unix:/run/dovecot2/lmtp"; + }; + masterConfig = + let + mkKeyVal = opt: val: [ + "-o" + (opt + "=" + val) + ]; + mkOpts = opts: lib.concatLists (lib.mapAttrsToList mkKeyVal opts); + in + { + "127.0.0.1:${toString config.networking.ports.smtp-starttls}".args = lib.mkBefore (mkOpts { + smtpd_sasl_auth_enable = "yes"; + smtpd_sasl_type = "dovecot"; + smtpd_sasl_path = "/run/dovecot2/auth-postfix"; + }); + }; + }; + + systemd.tmpfiles.rules = [ + "d ${maildir} 0700 ${cfg.mailUser} ${cfg.mailGroup} -" + ]; + + services.dovecot2 = { + enable = true; + modules = [ pkgs.dovecot_pigeonhole ]; + mailUser = "dovemail"; + mailGroup = "dovemail"; + sieve = { + extensions = [ "fileinto" ]; + scripts = { + after = builtins.toFile "after.sieve" '' + require "fileinto"; + if header :is "X-Spam" "Yes" { + fileinto "Junk"; + stop; + } + ''; + }; + }; + enableLmtp = true; + enablePAM = false; + enableDHE = false; + mailPlugins.perProtocol.lmtp.enable = [ "sieve" ]; + mailLocation = "maildir:~"; + mailboxes = { + Drafts = { + auto = "subscribe"; + specialUse = "Drafts"; + }; + Sent = { + auto = "subscribe"; + specialUse = "Sent"; + }; + Trash = { + auto = "subscribe"; + specialUse = "Trash"; + }; + Junk = { + auto = "subscribe"; + specialUse = "Junk"; + }; + Archive = { + auto = "subscribe"; + specialUse = "Archive"; + }; + }; + pluginSettings = { + sieve_after = "/var/lib/dovecot/sieve/after"; + }; + extraConfig = '' + listen = 127.0.0.1 + haproxy_trusted_networks = 127.0.0.1/8 + + default_internal_user = ${cfg.user} + default_internal_group = ${cfg.group} + + auth_username_format = %Ln + mail_home = ${maildir}/%u + + service imap-login { + unix_listener imap-caddy { + mode = 0666 + } + inet_listener imap { + port = 0 + } + inet_listener imaps { + port = 0 + } + } + + service auth { + unix_listener auth-postfix { + mode = 0660 + user = postfix + group = postfix + } + } + + userdb { + driver = static + args = uid=${cfg.mailUser} gid=${cfg.mailGroup} + } + + passdb { + driver = passwd-file + args = ${config.sops.secrets."mail/dovecot-passdb".path} + } + ''; + }; + + services.caddy.virtualHosts."${config.networking.fqdn}" = { }; + + services.caddy.globalConfig = '' + layer4 { + :${toString config.networking.ports.imap-tls} { + route { + tls { + connection_policy { + alpn imap + match { + sni ${config.networking.fqdn} + } + } + } + proxy { + upstream unix//run/dovecot2/imap-caddy + } + } + } + :${toString config.networking.ports.smtp-tls} { + route { + tls { + connection_policy { + match { + sni ${config.networking.fqdn} + } + } + } + proxy { + proxy_protocol v2 + upstream 127.0.0.1:${toString config.networking.ports.smtp-starttls} + } + } + } + } + ''; + + services.restic.backups.b2.paths = [ maildir ]; +} diff --git a/nixos/profiles/services/mail/postfix.nix b/nixos/profiles/services/mail/postfix.nix new file mode 100644 index 0000000..397b628 --- /dev/null +++ b/nixos/profiles/services/mail/postfix.nix @@ -0,0 +1,84 @@ +# Portions of this file are sourced from +# https://github.com/NickCao/flakes/blob/3b03efb676ea602575c916b2b8bc9d9cd13b0d85/nixos/hcloud/iad0/postfix.nix +{ + config, + lib, + pkgs, + ... +}: +{ + systemd.services.postfix.serviceConfig = { + PrivateTmp = true; + ExecStartPre = '' + ${pkgs.openssl}/bin/openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /tmp/selfsigned.key -out /tmp/selfsigned.crt -batch + ''; + }; + + services.postfix = { + enable = true; + hostname = config.networking.fqdn; + mapFiles.senders = builtins.toFile "senders" '' + rebmit@rebmit.moe rebmit + ''; + mapFiles.aliases = builtins.toFile "aliases" '' + abuse@rebmit.moe rebmit@rebmit.moe + hostmaster@rebmit.link rebmit@rebmit.moe + hostmaster@rebmit.moe rebmit@rebmit.moe + noc@rebmit.moe rebmit@rebmit.moe + postmaster@rebmit.link rebmit@rebmit.moe + postmaster@rebmit.moe rebmit@rebmit.moe + ''; + config = { + smtp_tls_security_level = "may"; + + smtpd_tls_chain_files = [ + "/tmp/selfsigned.key" + "/tmp/selfsigned.crt" + ]; + smtpd_tls_security_level = "may"; + smtpd_relay_restrictions = [ + "permit_sasl_authenticated" + "defer_unauth_destination" + ]; + + virtual_mailbox_domains = [ + "rebmit.moe" + "rebmit.link" + ]; + virtual_alias_maps = "hash:/etc/postfix/aliases"; + + lmtp_destination_recipient_limit = "1"; + recipient_delimiter = "+"; + disable_vrfy_command = true; + + milter_default_action = "accept"; + internal_mail_filter_classes = [ "bounce" ]; + }; + masterConfig = + let + mkKeyVal = opt: val: [ + "-o" + (opt + "=" + val) + ]; + mkOpts = opts: lib.concatLists (lib.mapAttrsToList mkKeyVal opts); + in + { + lmtp = { + args = [ "flags=O" ]; + }; + "127.0.0.1:${toString config.networking.ports.smtp-starttls}" = { + type = "inet"; + private = false; + command = "smtpd"; + args = mkOpts { + smtpd_tls_security_level = "none"; + smtpd_sender_login_maps = "hash:/etc/postfix/senders"; + smtpd_client_restrictions = "permit_sasl_authenticated,reject"; + smtpd_sender_restrictions = "reject_sender_login_mismatch"; + smtpd_recipient_restrictions = "reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject"; + smtpd_upstream_proxy_protocol = "haproxy"; + }; + }; + }; + }; +} diff --git a/nixos/profiles/services/mail/rspamd.nix b/nixos/profiles/services/mail/rspamd.nix new file mode 100644 index 0000000..2c7988f --- /dev/null +++ b/nixos/profiles/services/mail/rspamd.nix @@ -0,0 +1,60 @@ +# Portions of this file are sourced from +# https://github.com/NickCao/flakes/blob/3b03efb676ea602575c916b2b8bc9d9cd13b0d85/nixos/hcloud/iad0/postfix.nix +{ config, ... }: +{ + sops.secrets."mail/dkim-20241219" = { + sopsFile = config.sops.secretFiles.host; + path = "/var/lib/rspamd/dkim/20241219.key"; + owner = config.services.rspamd.user; + }; + + services.postfix.config = { + smtpd_milters = [ "unix:/run/rspamd/postfix.sock" ]; + non_smtpd_milters = [ "unix:/run/rspamd/postfix.sock" ]; + }; + + services.rspamd = { + enable = true; + workers = { + controller = { + bindSockets = [ "localhost:${toString config.networking.ports.rspamd-controller}" ]; + }; + rspamd_proxy = { + bindSockets = [ + { + mode = "0666"; + socket = "/run/rspamd/postfix.sock"; + } + ]; + }; + }; + locals = { + "worker-controller.inc".text = '' + secure_ip = ["127.0.0.1", "::1"]; + ''; + "worker-proxy.inc".text = '' + upstream "local" { + self_scan = yes; + } + ''; + "redis.conf".text = '' + servers = "127.0.0.1:${toString config.networking.ports.rspamd-redis}"; + ''; + "classifier-bayes.conf".text = '' + autolearn = true; + ''; + "dkim_signing.conf".text = '' + path = "${config.sops.secrets."mail/dkim-20241219".path}"; + selector = "20241219"; + allow_username_mismatch = true; + allow_envfrom_empty = true; + ''; + }; + }; + + services.redis.servers.rspamd = { + enable = true; + bind = "127.0.0.1"; + port = config.networking.ports.rspamd-redis; + }; +} diff --git a/secrets/hosts/suwako-vie0.yaml b/secrets/hosts/suwako-vie0.yaml index fe353eb..bc7bca4 100644 --- a/secrets/hosts/suwako-vie0.yaml +++ b/secrets/hosts/suwako-vie0.yaml @@ -2,6 +2,9 @@ synapse: signing-key: ENC[AES256_GCM,data:yFxwWDpdQvHetThkK02a/GN3lcw4GNb7BItutO5zisKptG6qB+BdWwHB34oK81J5Rbt3MGLwMwVpa0w=,iv:pQMDF4wSyzLvlRj3jMVbjyx16G76gj7e2ZvEHTB2VUU=,tag:dl1Onm5LNzH2aHZNfnRPbg==,type:str] oidc-client-secret: ENC[AES256_GCM,data:1zUxCuFyTWFvcu7W0dJ70RKyPWW0WY9fJwlaQkYRzok=,iv:8+3w1kz81CfTvzYv8thd/EaEUn2A/OdL8Uw4n0o69tE=,tag:qGTZodnQwOsI/cyXK6X09Q==,type:str] mautrix-telegram-bot-token: ENC[AES256_GCM,data:SgzTnwfmJqYeAM0PjZ0sosYTgkiw8gR6eszfkpM7VIOTlNmkkJezD5CtSHlsQA==,iv:olLvkkl9VHPrUuKZgOQgpzRMEymm9oYo0hJs8KsiTBE=,tag:eEOjwT7vBTyTRnS6qtu4dg==,type:str] +mail: + dkim-20241219: ENC[AES256_GCM,data:GjnSHIn9D4AFRsgin8AZyyXnerJrtChMAzEsMKVjaagzwAaNNdcMik+q7miAjY0UzO+N8Al/jDLaTmv+KWmGNr5nVcc7/EWNi1WsEYZsIZjk9baT2E5QVCZGE2URBKQI3BWl9yJTE5qyovJDOTwcIr77lAwoAGN+4QYflmtrEdSaurQXRBkiooK3g7QrDNUOaD3nJNpMED5ioFR4nC+8AB1RJmo/FfgRSgpgRUDE2Fv0Q7Ex2WDUKo7UsPguaxlrUGznshS9mPFKYt0Uxqno49OPhPHZAgjGH9UxDn3sduwyBNlv7ANGPslY0TrGq7G0PrWNhvvH3l/9Fu3pkULGyp6utjgx5eSmT8cSMpdaDbFyrFkk5nlpbvHKGeFRYLaJwo2gORt7qBVZxXgFtuOCUYsUI0fTAX6269tGXgBwlxr69IStXif043Wyh0wdsrekQ5PgMvWlTffkjQo7z57O931+gQiiFHuJWrREOQqcxMVlJmulLFEvCIZWbNvlTN5si+g9cMCR/uHsUpiFhgthNaYMHASf1PUvmfP1WOY7QQ2Bn37534inrZEqWLDCnAjpjFLYQs1ZjP2B3JmUT8AumIr/dY2CLRY7ojvv7QLNT8b7HOjEUAgIde3My4qRhV9LvGjV5DVaLJCgL9vHjCRV77YfO7QB/MZzQkQfX6R6mwQttG7FG/glOV6i8pdZb09Zs5BvmZ1KCuR8S+JgzjkSQ1/kJay/Wy5iLnRO1JaEp2xBFZPFHti7q00pJOJm/v7ebKhqudokyOK9mOGe7cpGww8CJ3wjbBiSGeImtzmx599DaYkJQZNVaP3rffVxAs3oXyn4ZTGYn6S+wNFOwNSw5TRuk2JaeZoCgFs8kP8BhuH/yoUP3gXHE/5t0U3ssciBqlhFxvKHvXHlcB3Wnq1990ROVe+p/O5ET2jbP3jdMNhh7UmTcGUdZtLTu8MphI9KSNeVcUWX1Taj9jX9RE4pNpZKIrsa4njpqvwXUijDRaAyXckHlmTFNB4R3UtG3wKTXe4nbfyVQFSPRkIGSDUXj0oyPJRnf0D3IC2PfNoJHioG+bCMzICrZ8mCQNtjVMlZBSgqsqdwSxvCYs0sO6GlWJo9fsWErCdzf/HU8wHnZ/X56yWYlAsIqx2V0qAZRIkw1hdwcnVh6smcgciZiXRPbCo6mwhtOOw5vXp1DKBEGHjwdZUsuXw+Zi1axCFuxYFM6gHs24//+lcfmv2VPNjopAeUdVvthPWg58CbLMTSxwKlFafTyVXsZZjHinZ23YNpzynz4EMlx5HTZ9jBEPdA7oENtafUI8CBxB7DhKvX0zC0286Y+w1a7Wkyxv9XFP9M6cn17nCH+HN03XQuNcVVNLzGzVapkJPnRgFxBe8o6B3/AyhIbY7phj/Z82E4tI23+Dwg6vCKQBBHXpLERU3j/UXtCaF7tFwin5kL6fai/bu7MC+/8olB7XrHHoa2raXqYZ22LISOgOScR2cVDDTwr+XZUca98bsDQVLjhZlcG7wrartOKRF0/fBZMnyMMrg9RAo0CMkXiPNIg+Sg/WkgUWefrJysCgUtPcJPHUVnYsqwxj+nSTFsX1yc4NWatmMkj7+maHd2F7BL3eTk4zdSMWaI9oxagbo869VD0wNtnsSIdhzummiILhQGAMpSrmLmFb+4GXVMJx1tljhTHh2zxHtTR/YlPr+pAxS2h21Mj1LVCxvNXsLc4bU5zyl6i/NBEimax05BFPX9fAiksSAcruoHPG3fzQJzSVfziN67xfwCTz4QGb0hneCCLWRvhp0OnH2DbeQdUPGiJVwaQZGn2B+ItNYjZ03I9h9BkRxunp/zTejHnW2eaZ7esibTpjxrPgUkrR9ZMLwVU9CTtTuT80f6njmQLbS6ubwnYgCtYkZ+iiQzWLtQ+Fa0QEHZQHx5zUtxFszElaxj+FIl3aJwPGZeAIvurQFsqOLD/Q1vjHVa3ag6VRhFDwFQOLTRl2ccbKi38RX/Kribnezx8PJYr7qpIsNtYQ1kd46B9lPcqyavIPwcOsLPL3ShC0rXkiguwTKaatR/RbTMvbbQQx4ryvxL3M5kCx6seHMmUX3jI+pJLKV4W6lc80ipZ+n/CdFEmksHSj5i+cNVcFne1RWMltiV/3jB8no6BneaeLaEXdMaEGmbfXbtOduIqdTnX5MIpF1xytKpCtZRICCs2dq+oNEyL8aew9VgyvoBh9Q1IKA75FTwo1kjgULHnfcQu4y29UNRciZn0ClzJcuwntLh6CnAwBbYgBi5,iv:ylnoYDXMVcmAWkIHTjjZM0rwV0PretOTxv1oP3HyfrM=,tag:KgToEhowEwQiG8KuWTdjrA==,type:str] + dovecot-passdb: ENC[AES256_GCM,data:C5YKPo13aEuTwMqzbMdn+r3vPrB5DtRi8hwCx2gbWOQ1bSn9d9yHJ7tNQwTGT5wGoQ3WZrs+mArTsrOF7OPzbXxguSr45Wns+ShdH4iypIoldOlc+bscafF9RmmmDrinyTjDezrI9ddTUNFFtVhkKi3pMi7yfoLTuaRuvQiVtF8=,iv:y+GikkIrFGecRdTeAKW0RUC7wqmkIeh5S++Q2LDVJS0=,tag:9Zw/GfvSPdLc1Zh0U3CJWw==,type:str] sops: kms: [] gcp_kms: [] @@ -26,8 +29,8 @@ sops: ZjQyOG1ZVDVnTGxBNWR0RGs3d082aGsKqqIdYDPsnvCa5+YFWCqdwAi5vgWuMazv sZF1K96MHFgxgqgGonu2wZN3uj2mGttDRC8ZZmMPEftY1na6VLl40A== -----END AGE ENCRYPTED FILE----- - lastmodified: "2024-12-18T15:24:09Z" - mac: ENC[AES256_GCM,data:S95HJPOv7L4EvIPRET4vgg6aLW4eGxPywshE9LnVWLHJLXU0ttVx/lbCtCHgO4sdyL1HZsevFWVcixQhwFSH+IoIClS8hUxvhMTEhUDctKtKdRu+JnhX6GlVhCQ4lc3r7q5+wbAGRUu1y8gwyjXmfP/LJQAhfTXlvnHdLyDvP2U=,iv:swUhs97E/34yjcQOCWgZCAqmCmPVW7spavF8xYa3qMQ=,tag:ZkhGAWt1Dj3TPJVMMTy4KQ==,type:str] + lastmodified: "2024-12-19T11:27:01Z" + mac: ENC[AES256_GCM,data:VQEnYBKOZ8X5VC4u8X8V0k6EU0Jvb0UipNw6RR6ZXQlclQn4c9Bbxzr8WU3lx0sNVHdY6aleHOKyWa0H0Bzyl5uLPu0v0SNtcB/JphbfJDNKZ6qwKtBSK8NUmSxLz35qoqRU/9pN1LFAW2DvzR6RCwk3RNLstuyT9XJWpuE9bmw=,iv:ClPVKD4f5KqQbVI+EYpAojHu4QuWAsrRMRX5GhS6o6I=,tag:iwe0CC013V0JyBMt+RUfhA==,type:str] pgp: [] unencrypted_suffix: _unencrypted version: 3.9.1 diff --git a/zones/common.nix b/zones/common.nix index 6268a6e..153b4dc 100644 --- a/zones/common.nix +++ b/zones/common.nix @@ -13,6 +13,25 @@ rec { minimum = 300; }; NS = map (name: "${name}.rebmit.link.") nameservers; + DKIM = [ + { + selector = "20241219"; + k = "rsa"; + p = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtLyv0K6sJv2aybXJAtmHyEEGdbTl58iTODDBAePKo10WI4B342QgfS0GWz7PmX/R/v0SK3fnpbG+VS9ZX8YTIEa0CZvnn9F7TcaIb8B6UkiELW9RAlDc8oNTk32EeTw/DZNATDXU1uin7Thea80YgXbbmB2X2HXZVw589YWbSfa9buHCEvxzx/ilIaQO2kf7/V9E9jcC/Ey0qQ7HF8Iyd3w9jKPaY0larzOrarkHGEmSxFPWBvZNlHOHa0cFW3HLT3cg5EzDwHrdcnqQmgHGbZWMMp1krEwPgTpbwYIQYuhADoNJSH6CktAc45wjFrzHQBAUY52YTR+ZjppWroTPcQIDAQAB"; + s = [ "email" ]; + } + ]; + DMARC = [ + { + p = "reject"; + sp = "reject"; + pct = 100; + adkim = "strict"; + aspf = "strict"; + fo = [ "1" ]; + ri = 604800; + } + ]; primary = "reisen-sea0"; secondary = [ diff --git a/zones/rebmit.link.nix b/zones/rebmit.link.nix index 9f76119..a6be420 100644 --- a/zones/rebmit.link.nix +++ b/zones/rebmit.link.nix @@ -11,28 +11,47 @@ dns.lib.toString "rebmit.link" { TTL SOA NS + DKIM + DMARC ; - subdomains = lib.listToAttrs ( - lib.mapAttrsToList ( - name: value: - lib.nameValuePair name { - A = value.endpoints_v4; - AAAA = value.endpoints_v6; - HTTPS = [ - { - alpn = [ - "h3" - "h2" + MX = with mx; [ (mx 10 "suwako-vie0.rebmit.link.") ]; + TXT = [ (with spf; soft [ "mx" ]) ]; + subdomains = + lib.recursiveUpdate + (lib.listToAttrs ( + lib.mapAttrsToList ( + name: value: + lib.nameValuePair name { + A = value.endpoints_v4; + AAAA = value.endpoints_v6; + HTTPS = [ + { + alpn = [ + "h3" + "h2" + ]; + } ]; } + ) publicHosts + ++ lib.mapAttrsToList ( + name: value: + lib.nameValuePair "${name}.enta" { + AAAA = [ value.enthalpy_node_address ]; + } + ) enthalpyHosts + )) + { + "suwako-vie0".DMARC = [ + { + p = "reject"; + sp = "reject"; + pct = 100; + adkim = "relaxed"; + aspf = "strict"; + fo = [ "1" ]; + ri = 604800; + } ]; - } - ) publicHosts - ++ lib.mapAttrsToList ( - name: value: - lib.nameValuePair "${name}.enta" { - AAAA = [ value.enthalpy_node_address ]; - } - ) enthalpyHosts - ); + }; } diff --git a/zones/rebmit.moe.nix b/zones/rebmit.moe.nix index 54364b1..79aff23 100644 --- a/zones/rebmit.moe.nix +++ b/zones/rebmit.moe.nix @@ -9,6 +9,8 @@ dns.lib.toString "rebmit.moe" { TTL SOA NS + DKIM + DMARC ; A = suwako-vie0.endpoints_v4; AAAA = suwako-vie0.endpoints_v6; @@ -20,6 +22,8 @@ dns.lib.toString "rebmit.moe" { ]; } ]; + MX = with mx; [ (mx 10 "suwako-vie0.rebmit.link.") ]; + TXT = [ (with spf; soft [ "mx" ]) ]; subdomains = { keycloak.CNAME = [ "suwako-vie0.rebmit.link." ]; matrix.CNAME = [ "suwako-vie0.rebmit.link." ];