Stalwart: Difference between revisions
m →Auto update TLSA records: correct typo |
rdns setup |
||
| (5 intermediate revisions by the same user not shown) | |||
| Line 5: | Line 5: | ||
|color=var(--border-color-warning) | |color=var(--border-color-warning) | ||
|background=var(--background-color-warning-subtle) | |background=var(--background-color-warning-subtle) | ||
|The content of this page are in reference to versions of Stalwart | |The content of this page are in reference to versions of Stalwart starting from v0.16. For legacy setup and migration instructions please refer follwing [[Stalwart/Legacy_setup]] page.}} | ||
== Setup == | |||
{{Warning|1=The Stalwart module for version the version >= 0.16 is not yet upstream and will be available in the upcoming NixOS 26.11 release.}}The following example enables the Stalwart mail server for the domain ''example.org'', listening on mail delivery SMTP/Submission (<code>25, 465</code>), IMAPS (<code>993</code>) and JMAP ports (8080/443) for mail clients to connect to. Mailboxes for the accounts <code>postmaster@example.org</code> and <code>info@example.org</code> get created if they don't exist yet. | |||
Most of the DNS entries are managed by Stalwart including TLS key generation. In this example we configure INWX as domain provider. For supported protocols and hosts see [https://github.com/stalwartlabs/dns-update upstream documentation]. | |||
== | {{file|||3=# FIXME: Workaround that inwx dns driver sends MX/CNAME/NS record | ||
# content as FQDN with trailing dot, which INWX api rejects. | |||
# Still broken upstream as of dns-update 0.5.8. | |||
nixpkgs.overlays = [ | |||
(final: prev: { | |||
stalwart_0_16 = prev.stalwart_0_16.overrideAttrs (oldAttrs: { | |||
cargoDeps = oldAttrs.cargoDeps.overrideAttrs (oldDeps: { | |||
buildCommand = (oldDeps.buildCommand or "") + '' | |||
substituteInPlace "$out/source-registry-0/dns-update-0.5.5/src/providers/inwx.rs" \ | |||
--replace-fail 'DnsRecord::CNAME(name) => ("CNAME", name.as_str().into_fqdn().into_owned(), None),' \ | |||
'DnsRecord::CNAME(name) => ("CNAME", name.as_str().into_name().into_owned(), None),' \ | |||
--replace-fail 'DnsRecord::NS(name) => ("NS", name.as_str().into_fqdn().into_owned(), None),' \ | |||
'DnsRecord::NS(name) => ("NS", name.as_str().into_name().into_owned(), None),' \ | |||
--replace-fail 'mx.exchange.as_str().into_fqdn().into_owned(),' \ | |||
'mx.exchange.as_str().into_name().into_owned(),' | |||
''; | |||
outputHash = "sha256-9AI+YSY85FgZhIxuhmZCUizGEhwYmC+O3iGwu3mv2Nk="; | |||
outputHashAlgo = "sha256"; | |||
outputHashMode = "recursive"; | |||
}); | |||
}); | |||
}) | |||
]; | |||
environment.etc = { | |||
"stalwart | "stalwart-admin-password".text = "mypassword"; | ||
"stalwart-inwx-password".text = "myinwxpass"; | |||
"stalwart | |||
}; | }; | ||
services.stalwart = { | services.stalwart = { | ||
enable = true; | enable = true; | ||
package = pkgs.stalwart_0_16; | |||
admin = { | |||
enable = true; | |||
username = "admin"; | |||
passwordFile = "/etc/stalwart-admin-password"; | |||
}; | }; | ||
url = "https://mail.example.org"; | |||
stateVersion = "26.11"; | |||
recovery.port = 8081; | |||
provision = | |||
let | |||
variant = type: value: { "@type" = type; } // value; | |||
in | |||
{ | |||
enable = true; | |||
url = "http://127.0.0.1:8080"; | |||
singletons = { | |||
SystemSettings = { | |||
defaultHostname = "mail.example.org"; | |||
defaultDomainId = "#main-domain"; | |||
}; | |||
Http.useXForwarded = true; | |||
MtaSts.mode = "enforce"; | |||
}; | }; | ||
listener = { | objects = { | ||
NetworkListener = { | |||
reconcile = true; | |||
match = [ "name" ]; | |||
objects = { | |||
listener-mgmt = { | |||
name = "management"; | |||
protocol = "http"; | |||
bind = [ "[::]:8080" ]; | |||
tlsImplicit = false; | |||
}; | |||
listener-smtp-relay = { | |||
name = "relay"; | |||
protocol = "smtp"; | |||
bind = [ "[::]:25" ]; | |||
tlsImplicit = false; | |||
}; | |||
listener-smtp-submissions = { | |||
name = "submissions"; | |||
protocol = "smtp"; | |||
bind = [ "[::]:465" ]; | |||
tlsImplicit = true; | |||
}; | |||
listener-imap = { | |||
name = "imap"; | |||
protocol = "imap"; | |||
bind = [ "[::]:993" ]; | |||
tlsImplicit = true; | |||
}; | |||
}; | |||
}; | }; | ||
DnsServer = { | |||
reconcile = false; | |||
match = null; | |||
objects = { | |||
dns-inwx = variant "Inwx" { | |||
description = "INWX"; | |||
username = "myuser"; | |||
password = variant "File" { | |||
filePath = "/etc/stalwart-inwx-password"; | |||
}; | |||
sandbox = false; | |||
}; | |||
}; | |||
}; | }; | ||
AcmeProvider = { | |||
reconcile = false; | |||
match = null; | |||
objects = { | |||
acme-letsencrypt = { | |||
directory = "https://acme-v02.api.letsencrypt.org/directory"; | |||
challengeType = "Dns01"; | |||
contact = [ "postmaster@example.org" ]; | |||
}; | |||
}; | |||
}; | }; | ||
Domain = { | |||
reconcile = true; | |||
match = [ "name" ]; | |||
objects = { | |||
main-domain = { | |||
isEnabled = true; | |||
name = "example.org"; | |||
catchAllAddress = "all@example.org"; | |||
reportAddressUri = "mailto:postmaster@example.org"; | |||
subAddressing = variant "Enabled" { }; | |||
dnsManagement = variant "Automatic" { | |||
dnsServerId = "#dns-inwx"; | |||
publishRecords = [ | |||
"mx" | |||
"spf" | |||
"dkim" | |||
"dmarc" | |||
"tlsa" | |||
"srv" | |||
"mtaSts" | |||
"tlsRpt" | |||
"autoConfig" | |||
"autoDiscover" | |||
# FIXME: Currently disabled since the CAA config | |||
# is very strict and would conflict with Caddy ACME | |||
# "caa" | |||
]; | |||
}; | |||
certificateManagement = variant "Automatic" { | |||
acmeProviderId = "#acme-letsencrypt"; | |||
}; | |||
dkimManagement = variant "Automatic" ( | |||
let | |||
days2millis = days: days * 24 * 60 * 60 * 1000; | |||
in | |||
{ | |||
selectorTemplate = "v{version}-{algorithm}-{date-%Y%m%d}"; | |||
rotateAfter = days2millis 90; | |||
retireAfter = days2millis 7; | |||
deleteAfter = days2millis 30; | |||
} | |||
); | |||
}; | |||
}; | |||
}; | }; | ||
Account = { | |||
reconcile = false; | |||
match = [ | |||
"name" | |||
"domainId" | |||
]; | |||
objects = { | |||
user-info = variant "User" { | |||
name = "info"; | |||
domainId = "#main-domain"; | |||
memberGroupIds = [ "#group-admin" ]; | |||
roles = variant "User" { }; | |||
credentials = [ (variant "Password" { secret = "mypassword"; }) ]; | |||
}; | |||
group-admin = variant "Group" { | |||
name = "admin"; | |||
domainId = "#main-domain"; | |||
description = "Administrators"; | |||
roles = variant "Default" { }; | |||
aliases = [ | |||
{ | |||
enabled = true; | |||
name = "postmaster"; | |||
domainId = "#main-domain"; | |||
} | |||
]; | |||
}; | |||
}; | |||
}; | }; | ||
}; | }; | ||
}; | }; | ||
}; | }; | ||
services.caddy = { | services.caddy = { | ||
enable = true; | enable = true; | ||
openFirewall = true; | |||
# Let's Encrypt account contact | |||
email = "postmaster@example.org"; | |||
virtualHosts."mail.example.org" = { | |||
serverAliases = [ | |||
"mta-sts.example.org" | |||
"autoconfig.example.org" | |||
"autodiscover.example.org" | |||
"ua-auto-config.example.org" | |||
]; | |||
extraConfig = '' | |||
reverse_proxy http://127.0.0.1:8080 | |||
''; | |||
}; | }; | ||
};|name=/etc/nixos/configuration.nix|lang=nix}} | }; | ||
# FIXME will get implemented as stalwart.openFirewall | |||
networking.firewall.allowedTCPPorts = [ | |||
25 | |||
465 | |||
587 | |||
993 | |||
];|name=/etc/nixos/configuration.nix|lang=nix}} | |||
Change the user and password of your DNS provider. The password for the <code>info@</code> mailbox and admin user is stored in plain-text here for demonstration purpose, please consider using a secret-management tool [[Comparison of secret managing schemes|such as agenix or sops-nix]]. | |||
=== DNS records === | === DNS records === | ||
Following DNS records need to be configured manually since they are not managed by Stalwart. | |||
{| class="wikitable" | {| class="wikitable" | ||
! Record Type | ! Record Type | ||
| Line 145: | Line 245: | ||
| ''IPv6 address of the mail server'' | | ''IPv6 address of the mail server'' | ||
| Required | | Required | ||
|- | |- | ||
| CNAME | | CNAME | ||
| Line 160: | Line 250: | ||
| example.org | | example.org | ||
| Mail host | | Mail host | ||
|} | |} | ||
=== rDNS setup === | |||
Configure rDNS in your VPS provider configuration dashbord to the IPv4 and IPv6 addresses, used in the DNS records above. | |||
=== DNSSEC === | === DNSSEC === | ||
| Line 231: | Line 259: | ||
For example, check if DNSSEC is working correctly for your new TLSA record | For example, check if DNSSEC is working correctly for your new TLSA record | ||
# nix shell nixpkgs#dnsutils --command delv _25._tcp. | # nix shell nixpkgs#dnsutils --command delv _25._tcp.mail.example.org TLSA @1.1.1.1 | ||
; fully validated | ; fully validated | ||
_25._tcp. | _25._tcp.mail.example.org. 10800 IN TLSA 3 1 1 7f59d873a70e224b184c95a4eb54caa9621e47d48b4a25d312d83d96 e3498238 | ||
_25._tcp. | _25._tcp.mail.example.org. 10800 IN RRSIG TLSA 13 5 10800 20230601000000 20230511000000 39688 example.org. He9VYZ35xTC3fNo8GJa6swPrZodSnjjIWPG6Th2YbsOEKTV1E8eGtJ2A +eyBd9jgG+B3cA/jw8EJHmpvy/buCw== | ||
=== Running behind reverse proxy === | === Running behind reverse proxy === | ||
| Line 289: | Line 317: | ||
== Tips and tricks == | == Tips and tricks == | ||
=== Sending from subaddresses === | === Sending from subaddresses === | ||