Add mail service to iron
This commit is contained in:
parent
19c3323445
commit
9ae4b0c539
12 changed files with 625 additions and 7 deletions
|
|
@ -36,7 +36,6 @@ with lib; {
|
|||
./services
|
||||
];
|
||||
config = {
|
||||
|
||||
system.stateVersion = "22.11";
|
||||
|
||||
security.sudo.wheelNeedsPassword = false;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,10 @@ duckdns-secret: ENC[AES256_GCM,data:SAf/xZ28tgmvqcVKC2tMNRm838AVMMNCC3fpYLXBEIoT
|
|||
sturzbach-htpasswd: ENC[AES256_GCM,data:qqBwu6mASnRqjy65knU4uIvBNXXgrfcmvWnbmOH4tVQ7vRbpEhe/GQDwAg==,iv:OQnDOzezjajGl35m/u5StQeMRR+1sNDD5u1my1wTngQ=,tag:7zjVRWI1IzZ5iS3sFHLubg==,type:str]
|
||||
navidrome-password-encryption-key: ENC[AES256_GCM,data:ynQsFyGDEBnlWhTlv0mF7mLiXOjijq9ixWWEa1OXsTOYAd74dU0dp3Fo532WtD4fPvIWEf8Y2dYmY7zPVLuydQ==,iv:GJqPVL5OIFPLMcCVOjWvMjyFR4iTXo3uGE8R0keTzG0=,tag:RTERQgYRxBBevlL2H1lIWA==,type:str]
|
||||
wireguard_key_hetzner-ha: ENC[AES256_GCM,data:ak/KpQIHBNRPriJ1IeKYXIp4CcnygRHSj5MzZNnuxQnVunmmtzGu0lBEajA=,iv:aNw3EooT6XE1zC+g37WSJasRCfnNUaKQrYCDBMTxRrg=,tag:KXc70tVFc7xDLlefk1Hzow==,type:str]
|
||||
hetzner-api-key: ENC[AES256_GCM,data:7eWYncujkEytQzhRdNRItPgpz1eUvcyp2PVLJtHbqd8=,iv:AxoKJUuor32kC3ZdpkDPUEUlPRosY6cKoWx0TIGK9wA=,tag:SVtXMraGxnJnx/j3zMQnQw==,type:str]
|
||||
rspamd-worker-controller: ENC[AES256_GCM,data:7tS8bEr9i5F+YZoj3uPQa6Xd2SCsuC+jE531AbKEmPHNeL3qMyO0pQZ/P1ONaPHTVMOPQHYABihDJcZv0BKW,iv:pFBVi4F661fnYPcCPwuetiGL1H+RAnJiFQhTUqGNwjU=,tag:xQoHIEQpnrMOnXqsH8anxQ==,type:str]
|
||||
dkim-keys:
|
||||
jalr.de.default: ENC[AES256_GCM,data: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,iv:2NBiTTW9slOH9BvM+kVbMB/+8EiS/Dc/eaqrtiwn4HY=,tag:0rc2+ZWy9XZYE7RK/oSo3g==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
|
|
@ -17,8 +21,8 @@ sops:
|
|||
TjdZRldhSzVtMkVoTzY1NjdGbCswRVUK0pi+8UuLqRmytcR2ikxOAM02iccl8P1y
|
||||
ixv0PKPLd+vQ23QeeQy/TfoGx16XttaDUnUrPLZR3TUKtAcld8+m6w==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2023-06-22T12:41:01Z"
|
||||
mac: ENC[AES256_GCM,data:OBzeE4XsdyrmW+U9nFLizAiNpdr7rXaBIa6q8PCjMMrGEi5C2Sg+1wHzgOqB3ACYc4gjv5W3s9rAVX3YOBEJ34eu+hcRWjLlK9tmKBdSZm1nP0gkfCmbMGw1DkPdkNRufX5FrIHEG0xzLN3Wo/C9LnDO+Qwn88OVq1+TYQHH3nY=,iv:OU+Xmmqsa03oRclRw/TCIXjroA/9YOtB07R9+1caUes=,tag:ZHEXxwz6NOzsA+jGT3oe4g==,type:str]
|
||||
lastmodified: "2023-06-28T01:59:15Z"
|
||||
mac: ENC[AES256_GCM,data:s/b3TPwtRDYb2QDZZXTD1QKuEDDFucFq6Ec2tHA1t7+20xS9NdhLb7rZkPTmOw/UJi+FF5+H63V9K1dIZr06FSofNCLidRJTV2q9TUKQeSyguIuu25xd47udiJ53sZeEwikw8iQx0bdkUKox9VQfw3+Eqqz/088XN5fNYGKq4ow=,iv:ZwRjSg2fWZCs3YOJhPkv2rha4UOaDAi6+vHc/LHWIPo=,tag:05/0W4cy/xSgsFBj+xtckQ==,type:str]
|
||||
pgp:
|
||||
- created_at: "2023-05-02T19:30:42Z"
|
||||
enc: |
|
||||
|
|
|
|||
BIN
hosts/iron/secrets/mail-users.nix
Normal file
BIN
hosts/iron/secrets/mail-users.nix
Normal file
Binary file not shown.
|
|
@ -3,6 +3,7 @@
|
|||
./dnsmasq.nix
|
||||
./dyndns.nix
|
||||
./jellyfin.nix
|
||||
./mail.nix
|
||||
./navidrome.nix
|
||||
./nginx.nix
|
||||
./public-ip-tunnel.nix
|
||||
|
|
|
|||
38
hosts/iron/services/mail.nix
Normal file
38
hosts/iron/services/mail.nix
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
{ config, pkgs, ... }:
|
||||
{
|
||||
sops.secrets.hetzner-api-key = {
|
||||
sopsFile = ../secrets.yaml;
|
||||
owner = "acme";
|
||||
};
|
||||
#sops.secrets."domain_key_jalr.de" = {
|
||||
# sopsFile = ../secrets.yaml;
|
||||
# owner = "rspamd";
|
||||
#};
|
||||
jalr = {
|
||||
mailserver = {
|
||||
enable = true;
|
||||
fqdn = "hha.jalr.de";
|
||||
domains = [
|
||||
{
|
||||
domain = "jalr.de";
|
||||
enableDKIM = true;
|
||||
}
|
||||
{
|
||||
domain = "fablab-nea.de";
|
||||
enableDKIM = false;
|
||||
}
|
||||
];
|
||||
users = import ../secrets/mail-users.nix;
|
||||
messageSizeLimit = 50 * 1024 * 1024;
|
||||
};
|
||||
};
|
||||
services.postfix.config = {
|
||||
smtp_bind_address = "159.69.103.126";
|
||||
smtp_bind_address_enforce = true;
|
||||
};
|
||||
|
||||
security.acme.certs."hha.jalr.de" = {
|
||||
dnsProvider = "hetzner";
|
||||
credentialsFile = pkgs.writeText "certbotCredentialsFile" "HETZNER_API_KEY_FILE=${config.sops.secrets.hetzner-api-key.path}";
|
||||
};
|
||||
}
|
||||
|
|
@ -11,8 +11,4 @@
|
|||
80
|
||||
443
|
||||
];
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults.email = "mail@jalr.de";
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
./kvm-switch-enable-screen.nix
|
||||
./libvirt.nix
|
||||
./localization.nix
|
||||
./mailserver
|
||||
./mute-indicator.nix
|
||||
./nix.nix
|
||||
./obs.nix
|
||||
|
|
@ -44,5 +45,12 @@
|
|||
boot.tmp.cleanOnBoot = true;
|
||||
|
||||
security.polkit.enable = true;
|
||||
|
||||
security.acme = {
|
||||
acceptTerms = true;
|
||||
defaults = {
|
||||
email = "security@jalr.de";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
107
modules/mailserver/default.nix
Normal file
107
modules/mailserver/default.nix
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.jalr.mailserver;
|
||||
in
|
||||
{
|
||||
options.jalr.mailserver = with lib; with lib.types; {
|
||||
enable = mkEnableOption "simple mail server";
|
||||
fqdn = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
FQDN of the mail server
|
||||
|
||||
It needs to have a matching reverse DNS record.
|
||||
By default, an acme certificate with this name has to be present.
|
||||
See `certDir` for more details.
|
||||
'';
|
||||
example = "mail.example.com";
|
||||
};
|
||||
storageDir = mkOption {
|
||||
type = path;
|
||||
description = "Location of the storage directory for mails";
|
||||
default = "/var/vmail";
|
||||
};
|
||||
domains = mkOption {
|
||||
type = listOf (submodule {
|
||||
options = {
|
||||
domain = mkOption {
|
||||
type = str;
|
||||
description = "Domain to serve";
|
||||
example = [ "example.com" "example.org" ];
|
||||
};
|
||||
enableDKIM = (lib.mkEnableOption "Enable DKIM signing") // { default = false; };
|
||||
DKIMSelector = mkOption {
|
||||
type = str;
|
||||
description = "DKIM selector to use when signing";
|
||||
default = "default";
|
||||
};
|
||||
};
|
||||
});
|
||||
description = "Domains of the mail server";
|
||||
};
|
||||
certDir = mkOption {
|
||||
type = path;
|
||||
description = "Directory with `fullchain.pem` and `key.pem` for the FQDN. Defaults to the ACME directory of the FQDN.";
|
||||
default = config.security.acme.certs."${cfg.fqdn}".directory;
|
||||
};
|
||||
users = mkOption {
|
||||
type = listOf (submodule {
|
||||
options = {
|
||||
address = mkOption {
|
||||
type = str;
|
||||
description = "Primary e-mail address of the user";
|
||||
example = "jdoe@example.com";
|
||||
};
|
||||
passwordHash = mkOption {
|
||||
type = str;
|
||||
description = ''
|
||||
Argon2id hash of the user’s password. Please note that it will be
|
||||
world-readable in the nix store.
|
||||
'';
|
||||
example = "$argon2id$v=19$m=2097152,t=9,p=4$ycAnTa3lq5EAPTNJVpZ3+A$dIJ0CHVNn3vRUUso3IveHlrzTURoudrkxU92P5Q9/P4";
|
||||
};
|
||||
aliases = mkOption {
|
||||
type = listOf str;
|
||||
description = ''
|
||||
A list of aliases for the user.
|
||||
|
||||
If multiple users have the same alias defined, mail will be
|
||||
delivered to both of them.
|
||||
'';
|
||||
default = [ ];
|
||||
example = [
|
||||
"j.doe@example.com"
|
||||
"jane.doe@example.com"
|
||||
"postmaster@example.com"
|
||||
];
|
||||
};
|
||||
};
|
||||
});
|
||||
description = "Users of the mail server";
|
||||
};
|
||||
cleanHeaders = mkOption {
|
||||
type = listOf str;
|
||||
description = "A list of regular expressions that define what headers are filtered";
|
||||
default = [
|
||||
"/^\\s*Received:/"
|
||||
"/^\\s*User-Agent:/"
|
||||
"/^\\s*X-Mailer:/"
|
||||
"/^\\s*X-Originating-IP:/"
|
||||
];
|
||||
};
|
||||
messageSizeLimit = mkOption {
|
||||
type = int;
|
||||
description = ''
|
||||
Message size limit, in bytes.
|
||||
'';
|
||||
default = 10485760;
|
||||
};
|
||||
};
|
||||
|
||||
imports = [
|
||||
./dovecot.nix
|
||||
./postfix.nix
|
||||
./rspamd.nix
|
||||
./users.nix
|
||||
];
|
||||
}
|
||||
162
modules/mailserver/dovecot.nix
Normal file
162
modules/mailserver/dovecot.nix
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.jalr.mailserver;
|
||||
postfixCfg = config.services.postfix;
|
||||
|
||||
passdb = pkgs.writeText "dovecot-users"
|
||||
(lib.concatMapStringsSep
|
||||
"\n"
|
||||
({ address, passwordHash, ... }: "${address}:${passwordHash}")
|
||||
cfg.users);
|
||||
in
|
||||
lib.mkIf cfg.enable {
|
||||
services.dovecot2 = {
|
||||
enable = true;
|
||||
|
||||
modules = with pkgs; [ dovecot_pigeonhole ];
|
||||
|
||||
enableLmtp = true;
|
||||
enablePAM = false;
|
||||
|
||||
mailUser = "vmail";
|
||||
mailGroup = "vmail";
|
||||
mailLocation = "maildir:${cfg.storageDir}/%d/%n";
|
||||
|
||||
sslServerCert = "${cfg.certDir}/fullchain.pem";
|
||||
sslServerKey = "${cfg.certDir}/key.pem";
|
||||
|
||||
mailboxes = {
|
||||
Archive = { specialUse = "Archive"; auto = "subscribe"; };
|
||||
Sent = { specialUse = "Sent"; auto = "subscribe"; };
|
||||
Drafts = { specialUse = "Drafts"; auto = "subscribe"; };
|
||||
Trash = { specialUse = "Trash"; auto = "subscribe"; };
|
||||
Spam = { specialUse = "Junk"; auto = "subscribe"; };
|
||||
};
|
||||
|
||||
sieveScripts = {
|
||||
before = pkgs.writeText "spam.sieve" ''
|
||||
require "fileinto";
|
||||
|
||||
if header :is "X-Spam" "Yes" {
|
||||
fileinto "Spam";
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
extraConfig = ''
|
||||
# generated 2021-02-04, Mozilla Guideline v5.6, Dovecot 2.3.13, OpenSSL 1.1.1i, intermediate configuration
|
||||
# https://ssl-config.mozilla.org/#server=dovecot&version=2.3.13&config=intermediate&openssl=1.1.1i&guideline=5.6
|
||||
ssl = required
|
||||
ssl_min_protocol = TLSv1.2
|
||||
ssl_cipher_list = ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES256-GCM-SHA384
|
||||
ssl_prefer_server_ciphers = no
|
||||
|
||||
protocol imap {
|
||||
mail_plugins = $mail_plugins imap_sieve
|
||||
}
|
||||
|
||||
protocol lmtp {
|
||||
mail_plugins = $mail_plugins sieve
|
||||
}
|
||||
|
||||
service imap-login {
|
||||
inet_listener imap {
|
||||
}
|
||||
}
|
||||
|
||||
service lmtp {
|
||||
unix_listener dovecot-lmtp {
|
||||
mode = 0600
|
||||
user = ${postfixCfg.user}
|
||||
group = ${postfixCfg.group}
|
||||
}
|
||||
}
|
||||
|
||||
passdb {
|
||||
driver = passwd-file
|
||||
args = scheme=argon2id username_format=%u ${passdb}
|
||||
auth_verbose = yes
|
||||
}
|
||||
|
||||
userdb {
|
||||
driver = static
|
||||
args = uid=vmail gid=vmail home=${cfg.storageDir}/%d/%n
|
||||
}
|
||||
|
||||
service auth {
|
||||
vsz_limit = 4G # needed for argon2.
|
||||
unix_listener auth {
|
||||
mode = 0660
|
||||
user = ${postfixCfg.user}
|
||||
group = ${postfixCfg.group}
|
||||
}
|
||||
}
|
||||
|
||||
service auth-worker {
|
||||
vsz_limit = 4G # needed for argon2.
|
||||
}
|
||||
|
||||
lda_mailbox_autosubscribe = yes
|
||||
lda_mailbox_autocreate = yes
|
||||
|
||||
plugin {
|
||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
||||
|
||||
${lib.optionalString cfg.spam.enable ''
|
||||
imapsieve_mailbox1_name = Spam
|
||||
imapsieve_mailbox1_causes = COPY
|
||||
imapsieve_mailbox1_before = file:/var/lib/dovecot/sieve/learn-spam.sieve
|
||||
|
||||
imapsieve_mailbox2_name = *
|
||||
imapsieve_mailbox2_from = Spam
|
||||
imapsieve_mailbox2_causes = COPY
|
||||
imapsieve_mailbox2_before = file:/var/lib/dovecot/sieve/learn-ham.sieve
|
||||
sieve_pipe_bin_dir = ${pkgs.symlinkJoin { name = "sieve-pipe-bin-dir"; paths = with pkgs; [ rspamd ]; } }/bin
|
||||
''}
|
||||
|
||||
sieve_global_extensions = +vnd.dovecot.pipe
|
||||
}
|
||||
'';
|
||||
};
|
||||
systemd.services.dovecot2 = {
|
||||
wants = [ "acme-finished-${cfg.fqdn}.target" ];
|
||||
after = [ "acme-finished-${cfg.fqdn}.target" ];
|
||||
|
||||
preStart = lib.mkIf cfg.spam.enable
|
||||
(lib.mkAfter
|
||||
(lib.concatStrings
|
||||
(lib.mapAttrsToList
|
||||
(name: content: ''
|
||||
cp ${pkgs.writeText name content} /var/lib/dovecot/sieve/${name}
|
||||
'')
|
||||
{
|
||||
"learn-spam.sieve" = ''
|
||||
require ["vnd.dovecot.pipe", "copy", "imapsieve"];
|
||||
pipe :copy "rspamc" ["learn_spam"];
|
||||
'';
|
||||
"learn-ham.sieve" = ''
|
||||
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||
|
||||
if environment :matches "imap.mailbox" "*" {
|
||||
set "mailbox" "''${1}";
|
||||
}
|
||||
|
||||
if string "''${mailbox}" "Trash" {
|
||||
stop;
|
||||
}
|
||||
|
||||
pipe :copy "rspamc" ["learn_ham"];
|
||||
'';
|
||||
})));
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
143 # IMAP
|
||||
];
|
||||
|
||||
security.acme.certs."${cfg.fqdn}".postRun = ''
|
||||
if systemctl is-active dovecot2; then
|
||||
systemctl --no-block reload dovecot2
|
||||
fi
|
||||
'';
|
||||
}
|
||||
162
modules/mailserver/postfix.nix
Normal file
162
modules/mailserver/postfix.nix
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.jalr.mailserver;
|
||||
|
||||
listToString = lib.concatStringsSep ",";
|
||||
|
||||
# List of attribute sets with single key-value pair
|
||||
plainAliases = (lib.flatten
|
||||
(map
|
||||
({ address, aliases, ... }:
|
||||
map
|
||||
(alias: { "${alias}" = address; })
|
||||
(aliases ++ lib.singleton address))
|
||||
cfg.users));
|
||||
|
||||
# Attribute set with every alias mapped to a list of receivers
|
||||
mergedAliases = (lib.attrsets.foldAttrs
|
||||
(val: col: lib.singleton val ++ col)
|
||||
[ ]
|
||||
plainAliases);
|
||||
|
||||
# Contents of the aliases file
|
||||
aliasesString = (lib.concatStringsSep
|
||||
"\n"
|
||||
(lib.mapAttrsToList
|
||||
(alias: addresses: "${alias} ${listToString addresses}")
|
||||
mergedAliases));
|
||||
|
||||
valiases = pkgs.writeText "valiases" aliasesString;
|
||||
|
||||
submissionHeaderCleanupRules = pkgs.writeText "submission_header_cleanup_rules"
|
||||
(lib.concatMapStringsSep
|
||||
"\n"
|
||||
(regex: "${regex} IGNORE")
|
||||
cfg.cleanHeaders);
|
||||
in
|
||||
lib.mkIf cfg.enable {
|
||||
security.dhparams.params.postfix = { };
|
||||
services.postfix = {
|
||||
enable = true;
|
||||
|
||||
enableSubmission = true; # plain/STARTTLS (latter is forced in submissionOptions)
|
||||
enableSubmissions = true; # submission with implicit TLS (TCP/465)
|
||||
|
||||
hostname = cfg.fqdn;
|
||||
networksStyle = "host";
|
||||
sslCert = "${cfg.certDir}/fullchain.pem";
|
||||
sslKey = "${cfg.certDir}/key.pem";
|
||||
|
||||
recipientDelimiter = "+";
|
||||
|
||||
mapFiles = {
|
||||
inherit valiases;
|
||||
};
|
||||
|
||||
config = {
|
||||
# General
|
||||
smtpd_banner = "${cfg.fqdn} ESMTP";
|
||||
disable_vrfy_command = true; # disable check if mailbox exists
|
||||
enable_long_queue_ids = true; # better for debugging
|
||||
strict_rfc821_envelopes = true; # only accept properly formatted envelope
|
||||
message_size_limit = toString cfg.messageSizeLimit;
|
||||
|
||||
virtual_mailbox_domains = listToString (map (x: x.domain) cfg.domains);
|
||||
virtual_mailbox_maps = "hash:/var/lib/postfix/conf/valiases";
|
||||
virtual_alias_maps = "hash:/var/lib/postfix/conf/valiases";
|
||||
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
|
||||
|
||||
smtpd_recipient_restrictions = listToString [
|
||||
"reject_non_fqdn_recipient"
|
||||
"reject_rbl_client ix.dnsbl.manitu.net"
|
||||
"reject_unknown_recipient_domain"
|
||||
"reject_unverified_recipient"
|
||||
];
|
||||
|
||||
smtpd_client_restrictions = listToString [
|
||||
"reject_rbl_client ix.dnsbl.manitu.net"
|
||||
"reject_unknown_client_hostname"
|
||||
];
|
||||
|
||||
smtpd_sender_restrictions = listToString [
|
||||
"reject_non_fqdn_sender"
|
||||
"reject_unknown_sender_domain"
|
||||
];
|
||||
|
||||
# generated 2021-02-04, Mozilla Guideline v5.6, Postfix 3.5.6, OpenSSL 1.1.1i, intermediate configuration
|
||||
# https://ssl-config.mozilla.org/#server=postfix&version=3.5.6&config=intermediate&openssl=1.1.1i&guideline=5.6
|
||||
smtpd_tls_security_level = "may";
|
||||
smtpd_tls_auth_only = "yes";
|
||||
smtpd_tls_mandatory_protocols = "!SSLv2, !SSLv3, !TLSv1, !TLSv1.1";
|
||||
smtpd_tls_protocols = "!SSLv2, !SSLv3, !TLSv1, !TLSv1.1";
|
||||
smtpd_tls_mandatory_ciphers = "medium";
|
||||
smtpd_tls_loglevel = "1";
|
||||
|
||||
tls_medium_cipherlist = listToString [
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256"
|
||||
"ECDHE-RSA-AES128-GCM-SHA256"
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384"
|
||||
"ECDHE-RSA-AES256-GCM-SHA384"
|
||||
"ECDHE-ECDSA-CHACHA20-POLY1305"
|
||||
"ECDHE-RSA-CHACHA20-POLY1305"
|
||||
"DHE-RSA-AES128-GCM-SHA256"
|
||||
"DHE-RSA-AES256-GCM-SHA384"
|
||||
];
|
||||
tls_preempt_cipherlist = "no";
|
||||
|
||||
smtpd_tls_dh1024_param_file = config.security.dhparams.params.postfix.path;
|
||||
};
|
||||
|
||||
# plain/STARTTLS (forced with smtpd_tls_security_level)
|
||||
submissionOptions = {
|
||||
smtpd_tls_security_level = "encrypt";
|
||||
smtpd_sasl_auth_enable = "yes";
|
||||
smtpd_sasl_type = "dovecot";
|
||||
smtpd_sasl_path = "/run/dovecot2/auth";
|
||||
#smtpd_sasl_security_options = "noanonymous, forward_secrecy"
|
||||
|
||||
smtpd_sender_login_maps = "hash:/etc/postfix/valiases";
|
||||
|
||||
smtpd_recipient_restrictions = listToString [ ];
|
||||
|
||||
smtpd_client_restrictions = listToString [
|
||||
"permit_sasl_authenticated"
|
||||
"reject"
|
||||
];
|
||||
|
||||
smtpd_sender_restrictions = listToString [
|
||||
"reject_sender_login_mismatch"
|
||||
];
|
||||
|
||||
cleanup_service_name = "submission-header-cleanup";
|
||||
};
|
||||
# implicit TLS
|
||||
submissionsOptions = config.services.postfix.submissionOptions;
|
||||
|
||||
masterConfig = {
|
||||
submission-header-cleanup = {
|
||||
private = false;
|
||||
maxproc = 0;
|
||||
command = "cleanup";
|
||||
args = [ "-o" "header_checks=pcre:${submissionHeaderCleanupRules}" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
25 # SMTP
|
||||
587 # SMTP submission
|
||||
];
|
||||
|
||||
systemd.services.postfix = {
|
||||
wants = [ "acme-finished-${cfg.fqdn}.target" ];
|
||||
requires = [ "dovecot2.service" ];
|
||||
after = [ "acme-finished-${cfg.fqdn}.target" "dovecot2.service" ];
|
||||
};
|
||||
|
||||
security.acme.certs."${cfg.fqdn}".postRun = ''
|
||||
if systemctl is-active postfix; then
|
||||
systemctl --no-block reload postfix
|
||||
fi
|
||||
'';
|
||||
}
|
||||
129
modules/mailserver/rspamd.nix
Normal file
129
modules/mailserver/rspamd.nix
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
cfg = config.jalr.mailserver;
|
||||
|
||||
# Generate DKIM keys:
|
||||
# nix shell nixpkgs#rspamd -c \
|
||||
# rspamadm dkim_keygen -s default -d example.com -b 4096 -k /dev/shm/dkim.key > dkim.txt
|
||||
|
||||
dkimEnabledDomains = (lib.filter (d: d.enableDKIM) cfg.domains);
|
||||
dkimSignatureDir = pkgs.stdenvNoCC.mkDerivation {
|
||||
name = "dkim-signatures";
|
||||
dontUnpack = true;
|
||||
installPhase = "mkdir $out" + "\n" + lib.concatStringsSep "\n" (
|
||||
map
|
||||
(
|
||||
x: "ln -s " + config.sops.secrets."dkim-keys/${x.domain}.${x.DKIMSelector}".path + " $out/${x.domain}.${x.DKIMSelector}.key"
|
||||
)
|
||||
dkimEnabledDomains
|
||||
);
|
||||
};
|
||||
in
|
||||
{
|
||||
options.jalr.mailserver.spam = {
|
||||
enable = (lib.mkEnableOption "spam filtering") // { default = true; };
|
||||
};
|
||||
|
||||
config = lib.mkIf (cfg.enable && cfg.spam.enable) {
|
||||
sops.secrets = lib.attrsets.listToAttrs
|
||||
(
|
||||
map
|
||||
(x:
|
||||
{
|
||||
name = "dkim-keys/${x.domain}.${x.DKIMSelector}";
|
||||
value = {
|
||||
owner = config.users.users.rspamd.name;
|
||||
sopsFile = ../../hosts + "/${config.networking.hostName}/secrets.yaml";
|
||||
};
|
||||
}
|
||||
)
|
||||
dkimEnabledDomains
|
||||
) // {
|
||||
rspamd-worker-controller = {
|
||||
owner = config.users.users.rspamd.name;
|
||||
sopsFile = ../../hosts + "/${config.networking.hostName}/secrets.yaml";
|
||||
};
|
||||
};
|
||||
|
||||
services.rspamd = {
|
||||
enable = true;
|
||||
postfix.enable = true;
|
||||
workers = {
|
||||
normal = {
|
||||
includes = [ "$CONFDIR/worker-normal.inc" ];
|
||||
bindSockets = lib.singleton {
|
||||
socket = "/run/rspamd/rspamd.sock";
|
||||
mode = "0660";
|
||||
owner = "${config.services.rspamd.user}";
|
||||
group = "${config.services.rspamd.group}";
|
||||
};
|
||||
};
|
||||
controller = {
|
||||
includes = [ "$CONFDIR/worker-controller.inc" ];
|
||||
bindSockets = [ "127.0.0.1:11334" ];
|
||||
};
|
||||
};
|
||||
locals = {
|
||||
"dkim_signing.conf".text = ''
|
||||
enabled = true;
|
||||
path = "${dkimSignatureDir}/$domain.$selector.key"
|
||||
selector = "default";
|
||||
allow_envfrom_empty = true;
|
||||
allow_hdrfrom_mismatch = false;
|
||||
allow_hdrfrom_multiple = false;
|
||||
allow_username_mismatch = false;
|
||||
sign_authenticated = true;
|
||||
sign_local = true;
|
||||
symbol = "DKIM_SIGNED";
|
||||
try_fallback = true;
|
||||
use_domain = "header";
|
||||
use_esld = true;
|
||||
use_redis = false;
|
||||
key_prefix = "DKIM_KEYS";
|
||||
check_pubkey = true;
|
||||
allow_pubkey_mismatch = false;
|
||||
'';
|
||||
"logging.inc".text = ''
|
||||
# starts at info, drops to notice once started up
|
||||
level = "silent";
|
||||
#debug_modules = ["dkim_signing"];
|
||||
'';
|
||||
"milter_headers.conf".text = ''
|
||||
extended_spam_headers = true;
|
||||
'';
|
||||
"multimap.conf".text = ''
|
||||
SENDER_BLOCKED {
|
||||
type = "from";
|
||||
filter = "email:addr";
|
||||
map = "/var/lib/rspamd/blocked_senders.map";
|
||||
symbol = "SENDER_BLOCKED";
|
||||
description = "Sender’s address is manually blocked";
|
||||
prefilter = true;
|
||||
action = "reject";
|
||||
score = 30.0;
|
||||
}
|
||||
SENDER_DOMAIN_BLOCKED {
|
||||
type = "from";
|
||||
filter = "email:domain:tld";
|
||||
map = "/var/lib/rspamd/blocked_sender_domains.map";
|
||||
symbol = "SENDER_DOMAIN_BLOCKED";
|
||||
description = "Sender’s effective second level domain is manually blocked";
|
||||
score = 8.0;
|
||||
}
|
||||
'';
|
||||
"redis.conf".text = ''
|
||||
servers = "127.0.0.1:${toString config.services.redis.servers.rspamd.port}"
|
||||
'';
|
||||
"worker-controller.inc".source = config.sops.secrets.rspamd-worker-controller.path; # includes password
|
||||
};
|
||||
};
|
||||
|
||||
services.redis = {
|
||||
vmOverCommit = true;
|
||||
servers.rspamd = {
|
||||
enable = true;
|
||||
port = 6379;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
12
modules/mailserver/users.nix
Normal file
12
modules/mailserver/users.nix
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
{ config, lib, ... }:
|
||||
|
||||
lib.mkIf config.jalr.mailserver.enable {
|
||||
users.users.vmail = {
|
||||
uid = 10000;
|
||||
group = "vmail";
|
||||
home = config.jalr.mailserver.storageDir;
|
||||
createHome = true;
|
||||
};
|
||||
|
||||
users.groups.vmail.gid = 10000;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue