add mail role

This commit is contained in:
asyncnomi 2025-07-31 17:36:58 +02:00
parent 6525887058
commit ca62eb6639
6 changed files with 259 additions and 5 deletions

16
secrets/dns/challenge.age Normal file
View file

@ -0,0 +1,16 @@
age-encryption.org/v1
-> ssh-ed25519 VQDASQ mK2dVgCVFtLPW3eaRemGvmV8XRCXjvlWgh3CyEP/HRs
Iv49IVQqzO14ggd3yqTbjEzEhWxLlxdeXEKATO6i3sE
-> ssh-ed25519 PV+Tsg vuk/IMPh7O6erZ4/lWAAeVQODnMFFIOrfY0fLOXT6Sk
B13BqD51YsPppMEJRnApEQ4N20YWSzHLv3YOGvXpERE
-> ssh-ed25519 Ecp4NA medZ8WVt4uwDLfFtxnpGbbBwP/JZQlWoHYfbuDtb0Us
JjKUAZUMYK4vXaS6QFVZVKoQO+zO7+kUyJGXC050yvE
-> ssh-ed25519 vHebMw FkWAklfnd60izhTwSPkmBZqMawBgcmG3p4xWhcUFElc
1TBv5EfHjaJFY/7kpDt7MBBB/svS2zqlNw3/4HkDxfk
-> ssh-ed25519 um7xWA lcSH23MCgWy3YevbzzZeOI9S7U1uzOwJvSagskSyl2k
7BoMoGFVx/I6P6kQE0Ku4wXMASCIdioX19n9BOlEoNM
-> ssh-ed25519 oRtTqQ QRA+pNeSutaGDIE1Ov2e6z/6/zwWnhhN+iTQoABvWgc
gTxHrYwKwr6sQXA5ySKWav8+IYxw+NN+54q+3fc6C4I
--- qrDkUs3UczTG7EDiOxbULZQ8+O4aAP6J6a6ys2tuyjI
t<02>zOêb3ãÿì<C3BF> ¸¾Ò'ÍW7bôš,Uo*<2A>ßZ%;ÆÊ–Ý?F· © {ùÂMôNg:šênEXᢗz•— óÜÆ\Îr¹-
ð¹Ð <05>5«ðýáìC²N¡˜ Ð7ræ^ÜòMgÀ>}£ËmÞÖ<C39E>Ã`±°‘

View file

@ -0,0 +1,16 @@
age-encryption.org/v1
-> ssh-ed25519 VQDASQ iTY4Dv+Hhxdd7XBliAhAKvPvm96cZcKOqkbC1/PPZEA
buKTlrIQwx2O6dw0PO2U20dYwHfyjvb99IVp9xAKmCc
-> ssh-ed25519 PV+Tsg fEV9Wshu13hi5+GNCgQM8p9JSFm3N7vhZENxEXBdqWM
uXyz+OfdsLV4PEwjErBIR46cEzp4wYyB/q2XtIgh8ic
-> ssh-ed25519 Ecp4NA oaBz9Yd6fgz3wqf85RR1+m45Rj61t1OOiJ3J92WCsx8
4XSBmWE7VlkcvmProSMN1YJ67I2c2VDDZz46QYEiUVI
-> ssh-ed25519 vHebMw ngxp3WhtYOOCUdmqK3PUqVv94iSzQkcXwTkugQ6mzBI
PNZZbo9nXJmpe2ajwn8KQ101zP71bfcYNd3FdCS/+Fs
-> ssh-ed25519 um7xWA ukb8EQy2ksckyJI6XAPviKdiFj+KdiW2uyKo8W+ZV1k
62WU+yOy322tDkuodVdq7405lt+PlUMCOEom3/0JbR0
-> ssh-ed25519 oRtTqQ liDAk9KgFQLzcNDa+EKeLMoXHJoc1UnPrhSd7iPWDgo
0x/LIAuRb4f5ebWREJc5XwP2UmObNlaQAR1s0vW1WH4
--- +Mt/wbUkihs5CGurWGR2IWjolm2APznuFHs7tUR+teY
á(/wr\Ü<>à
<EFBFBD>YyÔÓ<EFBFBD>¥Ö¯Mðª?<3F>Í#>Îg€fMÇxÛ“F7¹õ«™L?Â_KĈŠâKn`‚œÿ/Pþ¼ì9üGbxAN

View file

@ -97,8 +97,11 @@ in
# DNS secrets # DNS secrets
"dns/tsig.age".publicKeys = system-dns ++ users; "dns/tsig.age".publicKeys = system-dns ++ users;
# This is the secret of the tsig key isolated, if the tsig.age is modified, this one should be updated accordingly
"dns/challenge.age".publicKeys = system-mail ++ users;
# Mail secrets # Mail secrets
"mail/dkim.age".publicKeys = system-mail ++ users; "mail/dkim.age".publicKeys = system-mail ++ users;
"mail/mbox/test.age".publicKeys = system-mail ++ users;
} }

View file

@ -65,6 +65,14 @@ in
udp dport 53 accept udp dport 53 accept
'' else ""} '' else ""}
${if lib.elem myName mapping.mail.hosts then ''
# Mail server (without IMAP)
# With support for both SSL & STARTTLS
tcp dport 25 accept
tcp dport 465 accept
tcp dport 587 accept
'' else ""}
# Log anything else # Log anything else
ip protocol tcp counter log prefix "tcp.in.dropped: " ip protocol tcp counter log prefix "tcp.in.dropped: "
ip protocol udp counter log prefix "udp.in.dropped: " ip protocol udp counter log prefix "udp.in.dropped: "

View file

@ -1,4 +1,9 @@
{ ... }: { ... }:
let
ensureAccountsWithoutIMAP = [
"test@lasuite.federez.net"
];
in
{ {
# Import dependencies # Import dependencies
imports = [ imports = [

View file

@ -9,13 +9,219 @@ let
# And mapping # And mapping
mapping = import ./../../mapping.nix; 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 in
{ {
age.secrets = { age.secrets = generatedSecrets // {
"tsig" = { "challenge" = {
file = ./../../secrets/mail/tsig.age; file = ./../../secrets/dns/challenge.age;
owner = "knot"; owner = "acme";
group = "knot"; 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";
openFirewall = true;
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@lasuite.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.${nodes.${mapping.dns.master}.zone}.${nodes.${mapping.dns.master}.id}
RFC2136_TSIG_KEY=letsencrypt
RFC2136_TSIG_ALGORITHM=hmac-sha512.
RFC2136_TSIG_SECRET_FILE="${config.age.secrets.challenge.path}"
''}";
};
}; };
}; };
} }