Add mail forwarding based on alias files

This commit is contained in:
Gonne 2025-02-25 16:09:44 +01:00
parent d7b8f935cd
commit 7796b7aa00
6 changed files with 275 additions and 0 deletions

View file

@ -1,4 +1,6 @@
/*
* Forwarding mails: Update the Sops-secrets in the machine directory, rebuild on the VM and deploy.
* Everything else should happen automatically but new redirects might take up to two hours due HRZ infrastructure.
* Using the web admin interface: Set your SSH to do portforwarding of some local port to port 80 of the VM and
* and use your personal admin account or create one using the fallback admin password.
* Create users with mail boxes: Go to the admin interface and create them.
@ -9,6 +11,7 @@
{
config,
lib,
pkgs,
...
}: let
inherit
@ -26,6 +29,25 @@ in {
type = str;
description = "String containing the hashed fallback admin password";
};
domains = mkOption {
type = listOf (lib.types.submodule {
options = {
domain = mkOption {
description = "Domain name that we serve. We also push its addresses to HRZ.";
type = strMatching "^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$"; #Regex from https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch08s15.html
};
allowlistPass = mkOption {
description = "Password file for the HRZ API that gets a list of mailaddresses that we serve";
type = path;
};
virt_aliases = mkOption {
description = "File path to a virtual alias file applicable for this domain";
type = path;
default = "/dev/null"; # there might not be an alias file and reading an empty one works with our implementation
};
};
});
};
};
config = mkIf cfg.enable {
@ -127,6 +149,32 @@ in {
];
};
# Stalwart gets its configuration from two places: A TOML configuration file that we control in this module
# and from a database that can be configured from web management interface or via Rest API.
# We here define what comes from the TOML-file and especially add "sieve.trusted.scripts.*" to the default ones
# because only TOML-based keys may use macros to load files from disk.
# We want this to be able to load our sieve-script for mail forwarding.
config.local-keys =
[
"store.*"
"directory.*"
"tracer.*"
"server.*"
"!server.blocked-ip.*"
"authentication.fallback-admin.*"
"cluster.node-id"
"storage.data"
"storage.blob"
"storage.lookup"
"storage.fts"
"storage.directory"
"lookup.default.hostname"
"certificate.*"
] # the default ones
++ ["sieve.trusted.scripts.*"]; #for macros to be able to include our redirection script
sieve.trusted.scripts.redirects.contents = "%{file:/tmp/virt_aliases}%"; # generated redirect script
session.data.script = "'redirects'";
authentication.fallback-admin = {
user = "admin";
# see passwd on azathoth for plaintext or machine secret in encoded format for HTTP Basic AUTH
@ -149,6 +197,42 @@ in {
files = ["/root/.ssh/known_hosts"]; # for the backup server bragi
};
systemd = {
services = {
"stalwart-mail" = {
restartTriggers = lib.attrsets.mapAttrsToList (_: aliaslist: aliaslist.sopsFile) config.sops.secrets; # restart if secrets, especially alias files, have changed.
serviceConfig.PrivateTmp = lib.mkForce false; # enable access to generated Sieve script
};
"virt-aliases-generator" = {
description = "Virtual Aliases Generator: Generate a sieve script from the virtual alias file";
script = lib.strings.concatStringsSep "" (["${pkgs.alias-to-sieve}/bin/alias_to_sieve "] ++ map (x: "${x.virt_aliases} ${x.domain} ") cfg.domains ++ ["> /tmp/virt_aliases"]);
wantedBy = ["stalwart-mail.service"]; # Rerun on stalwart restart because forwardings may have changed.
serviceConfig = {
Type = "oneshot";
User = "stalwart-mail";
NoNewPrivileges = true;
# See https://www.man7.org/linux/man-pages/man5/systemd.exec.5.html
PrivateTmp = false;
ProtectHome = true;
ReadOnlyPaths = "/";
ReadWritePaths = "/tmp";
InaccessiblePaths = "-/lost+found";
PrivateDevices = true;
PrivateUsers = true;
ProtectHostname = true;
ProtectClock = true;
ProtectKernelTunables = true;
ProtectKernelModules = true;
ProtectKernelLogs = true;
ProtectControlGroups = true;
LockPersonality = true;
MemoryDenyWriteExecute = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
};
};
};
};
# Backups
services.borgbackup.jobs.mail = {
paths = [