226 lines
No EOL
7.8 KiB
Nix
226 lines
No EOL
7.8 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-${builtins.head (builtins.split "@" mbox)}".path}) ${mbox}
|
|
'';
|
|
|
|
maddyDeployCreds = pkgs.writeShellScriptBin "maddyDeployCreds" ''
|
|
${builtins.concatStringsSep "\n" (map generatedCredsCommand acct)}
|
|
'';
|
|
|
|
hostToDomain = hostname: "${builtins.replaceStrings ["-"] ["."] hostname}.net";
|
|
|
|
buildSecret = mbox: {
|
|
"mbox-${builtins.head (builtins.split "@" mbox)}" = {
|
|
file = ./../../secrets/mail/mbox + ( "/" + builtins.head (builtins.split "@" 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}"
|
|
''}";
|
|
};
|
|
};
|
|
};
|
|
} |