nix/shared/mail/maddy.nix

232 lines
No EOL
7.9 KiB
Nix

{ config, lib, pkgs, ensureAccountsWithoutIMAP, ... }:
let
# Import nodes
nodes = import ./../../nodes.nix;
myName = config.hostName;
myNode = nodes."${myName}";
# And mapping
mapping = import ./../../mapping.nix;
acct = ensureAccountsWithoutIMAP;
generatedCredsCommand = mbox: ''
${pkgs.maddy}/bin/maddyctl creds remove --yes ${mbox}
${pkgs.maddy}/bin/maddyctl creds create --password $(cat ${config.age.secrets."mbox-${lib.lists.head (lib.strings.splitString "@" mbox)}".path}) ${mbox}
'';
maddyDeployCreds = pkgs.writeShellScriptBin "maddyDeployCreds" ''
${lib.strings.concatStringsSep "\n" (map generatedCredsCommand acct)}
'';
hostToDomain = hostname: "${lib.replaceStrings ["-"] ["."] hostname}.net";
buildSecret = mbox: {
"mbox-${lib.lists.head (lib.strings.splitString "@" mbox)}" = {
file = ./../../secrets/mail/mbox + ( "/" + lib.lists.head (lib.strings.splitString "@" mbox) + ".age" );
owner = "maddy";
group = "maddy";
};
};
generatedSecrets = lib.foldl' (acc: mbox: acc // buildSecret mbox) {} acct;
in
{
age.secrets = generatedSecrets // {
"challenge" = {
file = ./../../secrets/dns/challenge.age;
owner = "acme";
group = "acme";
};
"dkim-keys" = {
file = ./../../secrets/mail/dkim.age;
mode = "440";
owner = "maddy";
group = "maddy";
};
};
services.maddy = {
enable = true;
hostname = "lasuite.federez.net";
user = "maddy";
group = "maddy";
primaryDomain = "lasuite.federez.net";
tls = {
loader = "file";
certificates = [{
keyPath = "/var/lib/acme/lasuite.federez.net/key.pem";
certPath = "/var/lib/acme/lasuite.federez.net/fullchain.pem";
}];
extraConfig = ''
protocols tls1.0 tls1.3
'';
};
config = ''
auth.pass_table local_authdb {
table sql_table {
driver sqlite3
dsn credentials.db
table_name passwords
}
}
table.chain local_rewrites {
optional_step regexp "(.+)\+(.+)@(.+)" "$1@$3"
optional_step static {
entry postmaster postmaster@$(primary_domain)
}
optional_step file /etc/maddy/aliases
}
msgpipeline local_routing {
default_destination {
reject 550 5.1.1 "We don't accept incoming email on lasuite.federez.net, please use federez.net ml"
}
}
smtp tcp://0.0.0.0:25 {
limits {
all rate 20 1s
all concurrency 10
}
dmarc yes
check {
require_mx_record
dkim
spf
}
source $(local_domains) {
reject 501 5.1.8 "Use Submission for outgoing SMTP"
}
default_source {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.1.1 "User doesn't exist"
}
}
}
submission tls://0.0.0.0:465 tcp://0.0.0.0:587 {
limits {
all rate 50 1s
}
auth &local_authdb
source $(local_domains) {
check {
authorize_sender {
prepare_email &local_rewrites
user_to_email identity
}
}
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
modify {
dkim {
debug yes
domains $(primary_domain) $(local_domains)
selector default
key_path ${config.age.secrets.dkim-keys.path}
}
}
deliver_to &remote_queue
}
}
default_source {
reject 501 5.1.8 "Non-local sender domain"
}
}
target.remote outbound_delivery {
limits {
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
}
}
}
target.queue remote_queue {
target &outbound_delivery
autogenerated_msg_domain $(primary_domain)
bounce {
destination postmaster $(local_domains) {
deliver_to &local_routing
}
default_destination {
reject 550 5.0.0 "Refusing to send DSNs to non-local addresses"
}
}
}
'';
# ensureAccounts automatically create an IMAP account which we do not wish
ensureAccounts = [];
ensureCredentials = {
# Be careful there: those are deployed at runtime only if they do not already exists
# As a result using agenix to deploy them will silently failed
# See impl: https://github.com/NixOS/nixpkgs/blob/nixos-25.05/nixos/modules/services/mail/maddy.nix
# We will write our own script.
# But avoid doing this:
# "test@lasuite.federez.net".passwordFile = config.age.secrets.monitoring.path;
# "test2@lasuite.federez.net".passwordFile = config.age.secrets.postmaster.path;
# ...
};
};
# Execute custom password setup script
systemd.services.maddyDeployCredsSrv = {
wantedBy = [ "multi-user.target" ];
after = [ "maddy.service" ];
description = "Add password to maddy's static account";
serviceConfig = {
Type = "oneshot";
User = "maddy";
ExecStart = "${maddyDeployCreds}/bin/maddyDeployCreds";
};
};
security.acme = {
acceptTerms = true;
defaults.email = "monitoring@federez.net";
certs = {
"lasuite.federez.net" = {
domain = "lasuite.federez.net";
# Needed for mta-sts compliance
extraDomainNames = map hostToDomain mapping.mail.hosts;
group = "maddy";
dnsProvider = "rfc2136";
reloadServices = [
"maddy"
];
dnsPropagationCheck = true;
enableDebugLogs = true;
environmentFile = "${pkgs.writeText "dns-creds" ''
RFC2136_NAMESERVER=172.19.${toString nodes.${mapping.dns.master}.zone}.${toString nodes.${mapping.dns.master}.id}
RFC2136_TSIG_KEY=letsencrypt
RFC2136_TSIG_ALGORITHM=hmac-sha512.
RFC2136_TSIG_SECRET_FILE="${config.age.secrets.challenge.path}"
''}";
};
};
};
fwtables.allowedTCPPorts = [
{ port = 25; public = true; }
{ port = 465; public = true; }
{ port = 587; public = true; }
];
}