Mail machine #47

Merged
Gonne merged 9 commits from Gonne/nixConfig:nyarlathotep into main 2025-02-27 15:59:49 +00:00
6 changed files with 275 additions and 0 deletions
Showing only changes of commit 7796b7aa00 - Show all commits

View file

@ -53,6 +53,12 @@
_module.args.pkgs = import inputs.nixpkgs {
inherit system;
config.permittedInsecurePackages = ["jitsi-meet-1.0.8043"];
overlays = [
(_: _: {
alias-to-sieve = inputs.alias-to-sieve.packages.x86_64-linux.default; # add custom package to convert alias files to sieve scripts on the stalwart machine
Gonne marked this conversation as resolved Outdated

The package selection should depend on system, and is there a reason we overlay like this and not
just use flake-inputs.alias-to-sieve... in the system config?

The package selection should depend on system, and is there a reason we overlay like this and not just use `flake-inputs.alias-to-sieve...` in the system config?

The flake-inputs parameter is not available as a module parameter while pkgs is supplied by the nix module system.

The `flake-inputs ` parameter is not available as a module parameter while `pkgs` is supplied by the nix module system.
})
];
};
};

View file

@ -12,6 +12,28 @@
enable = true;
# see passwd on azathoth for plaintext or machine secret in encoded format for HTTP Basic AUTH
stalwartAdminHash = "$argon2i$v=19$m=4096,t=3,p=1$d0hYOTkzclpzSmFTZUplWnhVeWE$I7q9uB19RWL0oZKaPlMPSlGfFp6FQ/vrx80FFKCsalg";
domains = [
# lists.mathebau.de is forwarded to another VM and does not need to be listed here.
{
domain = "matheball.de";
allowlistPass = config.sops.secrets."allowlistPass/matheball".path;
}
{
domain = "mathebau.de";
allowlistPass = config.sops.secrets."allowlistPass/mathebau".path;
virt_aliases = config.sops.secrets."mathebau.aliases".path;
}
{
domain = "mathechor.de";
allowlistPass = config.sops.secrets."allowlistPass/mathechor".path;
virt_aliases = config.sops.secrets."mathechor.aliases".path;
}
{
domain = "koma89.tu-darmstadt.de";
allowlistPass = config.sops.secrets."allowlistPass/koma".path;
virt_aliases = config.sops.secrets."koma.aliases".path;
}
];
};
networking.hostName = "kaalut";
@ -19,6 +41,25 @@
system.stateVersion = "24.05";
sops.secrets = {
# Virtual alias file
Gonne marked this conversation as resolved Outdated

The passwords can be in one yaml file and still be exposed as different secrets by sops. This can
be managed by the yaml structure

The passwords can be in one yaml file and still be exposed as different secrets by sops. This can be managed by the yaml structure
"mathebau.aliases" = {
sopsFile = ./mathebau.aliases.yaml;
owner = "stalwart-mail";
group = "stalwart-mail";
mode = "0440";
};
"mathechor.aliases" = {
sopsFile = ./mathechor.aliases.yaml;
owner = "stalwart-mail";
group = "stalwart-mail";
mode = "0440";
};
"koma.aliases" = {
sopsFile = ./koma.aliases.yaml;
owner = "stalwart-mail";
group = "stalwart-mail";
mode = "0440";
};
backupKey = {
sopsFile = ./backupKey.yaml;
owner = "root";

View file

@ -0,0 +1,48 @@
koma.aliases: ENC[AES256_GCM,data:r9dJzjUrv9BBr6emtHuIm71OamwTQdxdAbWuh62ZPG/tbvg8YimMvUno1WXn6EXn+0q2Gf+r4UiB6RJLD62D+JU+lEAKg9LKfX+578M4eCIbwRqkHWQYAtT/V3nB8L7/dqcvdatF90+50w==,iv:SL13OY8XUNdVBkZlKBfqwzT5LTtZcykyGIK+nHHOa10=,tag:Yyu2/ZqLotmFD+cMwtXYlA==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1rasjnr2tlv9y70sj0z0hwpgpxdc974wzg5umtx2pnc6z0p05u3js6r8sln
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBBS283ZTdKVTVLaDRDV1N5
SGhJQjJWdXJzc1l5OWtCWVdueTJMdjZpUjJzCmtUZFRYR0JXTW15Z0NyMktEbW5w
dkk1TjF0dVQ3MlFhNUFTbU0vMFdySWcKLS0tIDZPQmxSVGYzT2dDM244ek95dk9n
SnhtQWJic3B2YTM1ZlE3SHVRSjl1YVkKgUXW7JW3WSM5EusBoxQMsBRGwIqqi7Lo
DgWLq/P1rruuqRAS8hl4cht3jz6PlCJgVh2xpaM/kfkFS8ZuhVFw4g==
-----END AGE ENCRYPTED FILE-----
- recipient: age1epz92k2rkp43hkrg3u0jgkzhnkwx8y43kag7rvfzwl9wcddelvusyetxl7
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBKdmcyM3hSUFdlM25UUndu
RUhzdEhsakdEdytBUGRyRTFXRzdYK2RBR0dnCmJqOTlvYkZkeld3eDYvRmRmUU5u
aHArR0FkZWRtT0hoNTZpS1JmaTRHencKLS0tIGVVSWN0NWQyQWdrcXdQUnQxUjdu
MWFZWVQ3RmZZS3FnRkJPdDRrOTZrWG8KVgFqfeBLw5gTBKugfnC4a5OLwOhosSgy
3hXbGMrJiBDwOS+70H3L+IwiNSoJ6mL+ufShCTq8wER2L9GTteI8gg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1dhzugelagj6vge5jjxwwn0522ngf7fhxn04sxy2tm8557rtme5tstprwnj
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSAzamM5TDVQM0hnZklsbncx
SlBMM0NpcnBBai94czV5WE1Md21EeE1kVXpFClpDVTRqYm5rWFhjVjRPQm1IVWxW
WTNlZFo4Y3VVNjZhckZ0RFVlQlV0OEEKLS0tIGJOR3k0OUorYTNXL01KQWJBUzVD
V0xidWR0SnBDM01hRlkrTlY4eEIrc1EK1Hye/jrQebkEDQ8muJpgHqBLefjnEJPF
GxdANetJLuZeeiOUjaUcbP6tecqZpiWN8fFEXrjNL4vnrHvJ+bR1aA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ktwclxa640l89le6yecm8v2z6hmwr4lusd6x9gyzamhv57887szqtqp59a
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBqQURCeGJBYytCdlhrWjF5
c1ZrbEFENDF5bTNMaE52SE5CS1dVdWJCNlFzClZtK1QxOWY0dEVRRWY4MEtlZ1N1
eGlaYXVLMUJiUi9FckdNcllBRCt4cmMKLS0tIEZuOTZQTm9vWHQ4Y3Z6RVloT0VL
OW5ZQWIvU2x1OEN6OW84K0dqRmhGNUUKOA3ugnG/ZD7m1DKrFjpZ8opPnjPtLaQx
t8qgGuQIoX6KeUb+YybRAOAPPzl51/m9GSUB43Eanm/tVJpdaew7/g==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2025-02-25T17:41:29Z"
mac: ENC[AES256_GCM,data:lZ9AXtJzVuc8Jg9L0aGhS18cs8pTjOG/xNP2tG25/7/PEdEV1SNwbxubGQOFAHrNbiDbmJMKJq96mhV8e3tHszlrzQnU1uyu9MrWiAYwV3CjmwSqC4J9ezSm/AY9e9+OWKn6sb4RVsz9A7aDGUhhoZMycnPNRKlpTuzdTIJK98o=,iv:LxSsZoHkJ2HFXBLWkw+SUb/LYW2ciE1DtzpoV4YLOwQ=,tag:QeYmreRGZk4PqlLWJLLD8g==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.4

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,48 @@
mathechor.aliases: ENC[AES256_GCM,data:VKEGY6KVtgKApnV7N2e2cqy9erDWQ2fb88Gwcpp5th/t0VGp16KGDtGiuQXhY80j6dDIcQMd9bLHzqAzc4+i/WhmEPhiXUkGiEKuarMfvqNl1LBlXFCoIrUXMMSIqab9q+fE3ignVQapE/YZt9aniyvg1prcmBcwIy9rDoHkiTY006ux5CM+vX0F60ADX8Nf6Qmn/JncPxXgq2jYsBxjXPj7BwJaair/+nxrbVf0,iv:Elj1NDeR1fdIIjIbjvkV3BmcVAKjwdMfknuNxMXJsa4=,tag:AkXWQ8sTMLsd7a+MfRcF/w==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age1rasjnr2tlv9y70sj0z0hwpgpxdc974wzg5umtx2pnc6z0p05u3js6r8sln
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjMlRsWnkrREVaQitsWHMy
WHZFVG1qN25QbWFHcUxNS1Z0SFRDd1oxeG5RCi8wNUhkeWh2VjI4ZGowM1ExaExh
SE1yVGFTUHZadUdDL3pxaGdKTHQ0VTgKLS0tIHVNM2xlOFNNS3dFalJqZUtPODRn
b2NOTHpXSUVyaFRJNG5ONCt0TTVjOEkKYld7KN995QxdrGBVRYgCxO7kGwsiq+cp
iQJTjMdoFygIrTkgE5Rj89/GCiVe0+yAWJuQF7PEnC3cyq0M1g+fzw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1epz92k2rkp43hkrg3u0jgkzhnkwx8y43kag7rvfzwl9wcddelvusyetxl7
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPRFJCeXhwQVFSWmgzNHBu
SHlTTGtiRkI5bmhKa1B0QTZMY3FERmlUd0FBCk1vOUpydEFZUExpR2hpWm9mRHpE
dk9MQ042K0FpSVJ3dUlQcktGT2k1VjAKLS0tIHpGRmwzNE01YkV1TW94RkNmMjN4
YnNXZUlta3NMVW9Cc3V2T0t4R01RSlkKNTW3gnF49BuPwF3jwciOYThJe+gJa0a6
WKYt+aJuHi0a4y5rS/wfttij+hS5vYVNOrgfJ5bGinkNuAygA2hMOg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1dhzugelagj6vge5jjxwwn0522ngf7fhxn04sxy2tm8557rtme5tstprwnj
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6MjZOR1dwb3RjZnlNNW4v
SzJnT1BRVktWNDI5S2Z2NnhQQzdNeS9ralI0CnN0SU9ESEV3ZCtRQmpZK3VZOGYx
Y3FVUy9zY3RZcGxyVmttVzFJL1haYWsKLS0tIENGRW1KZkpUdldOZWgzSXVoenpX
dTVpNUpWallSTzJ3cEZJTXk3c2t1czgKzJCwhMspzAsjzwSRdSPUoseEAsKp8HFy
cL9if92ar68HMHTdoy0Zvy+5AbxKUxgXZ2t8cDgkL8bNG5Ri2xYaUA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1ktwclxa640l89le6yecm8v2z6hmwr4lusd6x9gyzamhv57887szqtqp59a
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBtNm5xUGkrK1dYd2ZtamFW
NXpNMEtvNTl3U3MzeVNSbVJOdGdlWGsxRHlZCllQVmNtYzBJNDc2Y0dmUlNsbTF5
RHB4QWZ1VGNFVkx1Q0hNK3FDTTRrUlkKLS0tIG9hbldDeHk0YmVZV2IwMXNpYStU
Q29uVHBCb2pTeWVJVmVXbWpycnFneWMKnDmu5917dddV8vjO0L8OP3wXMjDi46Ro
b9eOY8l74jm4sTxyKNvnkEjD6iHn1t7f8J7HAbWrpZY+J0i77nrzQw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-11-23T09:05:51Z"
mac: ENC[AES256_GCM,data:Xnulo0681LtgH9SZt9DL3nd9bSDH+TCQDvbKdggVBJ66rxBiKmlbu5MAblAWqxbdZ6EelldaVeX9OaL2rYJoYbTWxzw2iuPieldp3Ah3PsTI2C8W+UD9KVHcB+3AMOmVmJZzFlZvTwyfPfZRNNb0HAijkN97P3fP0r1Iqf3YjiI=,iv:vhu38HM4e+PyyChXvI87LWSGtKQQiXUr4MKrI7kotzk=,tag:eNuQD74kUO+duqEXNbLJBw==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.9.1

View file

@ -1,4 +1,6 @@
/*
* Forwarding mails: Update the Sops-secrets in the machine directory, rebuild on the VM and deploy.
Gonne marked this conversation as resolved Outdated

ufff

ufff
* Everything else should happen automatically but new redirects might take up to two hours due HRZ infrastructure.
Gonne marked this conversation as resolved Outdated

double uff

double uff
* 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.
Gonne marked this conversation as resolved Outdated

isn't really a problem, this means we have time to rebuild stalwart to deploy the new alias

isn't really a problem, this means we have time to rebuild stalwart to deploy the new alias
* 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 = {
Gonne marked this conversation as resolved Outdated

did you think about a more specialized type, non empty string? string matching regex? something like this?

did you think about a more specialized type, non empty string? string matching regex? something like this?
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
Gonne marked this conversation as resolved Outdated

nonempty?

nonempty?
};
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
};
};
});
};
Gonne marked this conversation as resolved Outdated

does this need to be a system wide package or can we just call it in the right places?

does this need to be a system wide package or can we just call it in the right places?

There is no need for system wide.

There is no need for system wide.
};
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.

does this only work for local ipv4 connections? That might change at some point, right?

does this only work for local ipv4 connections? That might change at some point, right?

Yes, but we probably want to give credentials to the VMs and remove the trust by IP anyways.

Yes, but we probably want to give credentials to the VMs and remove the trust by IP anyways.

Fine by me, but we will need to document somewhere how this is done. Else people after us will be lost

Fine by me, but we will need to document somewhere how this is done. Else people after us will be lost
# 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.*"
Gonne marked this conversation as resolved Outdated

Ok, so in the TOML file there is a list of options that defines which options are set in the remainder of the TOML file. I didn't read that from this comment. (And maybe my understanding is still wrong)

Ok, so in the TOML file there is a list of options that defines which options are set in the remainder of the TOML file. I didn't read that from this comment. (And maybe my understanding is still wrong)
"tracer.*"
"server.*"
"!server.blocked-ip.*"
"authentication.fallback-admin.*"
"cluster.node-id"
"storage.data"
"storage.blob"
"storage.lookup"
Gonne marked this conversation as resolved Outdated

comment is outdated

comment is outdated
"storage.fts"
"storage.directory"
Gonne marked this conversation as resolved Outdated

in production?

in production?
"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
};
Gonne marked this conversation as resolved Outdated

This line needs explanation. A LOT OF IT

This line needs explanation. A LOT OF IT
"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"]);
Gonne marked this conversation as resolved Outdated

A linkt to hrz docu, so we can look at it if something breaks

A linkt to hrz docu, so we can look at it if something breaks
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;
Gonne marked this conversation as resolved

can we have a comment for this regex?

can we have a comment for this regex?
RestrictRealtime = true;
RestrictSUIDSGID = true;
Gonne marked this conversation as resolved Outdated

and this one? is it the same?

and this one? is it the same?
};
};
};
};
# Backups
services.borgbackup.jobs.mail = {
paths = [