services/mail: init postfix, dovecot and rspamd

This commit is contained in:
Lu Wang 2024-12-19 21:26:19 +08:00
parent ba323843c8
commit 4177868f1c
Signed by: rebmit
SSH key fingerprint: SHA256:3px8QV1zEerIrEWHaqtH5rR9kjetyRST5EipOPrd+bU
9 changed files with 391 additions and 22 deletions

View file

@ -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

View file

@ -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;

View file

@ -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 ];
}

View file

@ -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";
};
};
};
};
}

View file

@ -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;
};
}

View file

@ -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: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,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

View file

@ -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 = [

View file

@ -11,8 +11,14 @@ dns.lib.toString "rebmit.link" {
TTL
SOA
NS
DKIM
DMARC
;
subdomains = lib.listToAttrs (
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 {
@ -34,5 +40,18 @@ dns.lib.toString "rebmit.link" {
AAAA = [ value.enthalpy_node_address ];
}
) enthalpyHosts
);
))
{
"suwako-vie0".DMARC = [
{
p = "reject";
sp = "reject";
pct = 100;
adkim = "relaxed";
aspf = "strict";
fo = [ "1" ];
ri = 604800;
}
];
};
}

View file

@ -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." ];