machines/nix/modules/nixos/infrastructure/mailserver/dkim.nix
Kaare Hoff Skovgaard fbcd590bfe
Final attempt at getting stalwart working before revert
Non working parts:

1. OIDC login, stalwart assumes the entire token is base64 encoded,
   which it is not.
2. Apparently there's no support for mixed directories, allowing both
   logins from ldap and from internal database. I want this in order
   to support accounts for services as well as persons.
2025-07-30 11:08:00 +02:00

232 lines
7.1 KiB
Nix

{
lib,
config,
...
}:
let
cfg = config.khscodes.infrastructure.mailserver;
publicKeyBegin = ''"-----BEGIN PUBLIC KEY-----\n"'';
publicKeyEnd = ''"-----END PUBLIC KEY-----\n"'';
rsaKeyPath = domain: "/run/secret/dkim/${domain}.snm_rsa.key";
ed25519KeyPath = domain: "/run/secret/dkim/${domain}.snm_ed25519.key";
keyFiles = lib.lists.flatten (
lib.lists.map (domain: [
(rsaKeyPath domain)
(ed25519KeyPath domain)
]) cfg.domains
);
ifthen = condition: expr: {
"if" = condition;
"then" = expr;
};
otherwise = expr: { "else" = expr; };
authDkimForDomain = domain: [
(ifthen "sender_domain = '${domain}'" "['${domain}_rsa', '${domain}_ed25519']")
];
authDkim = lib.lists.flatten (lib.lists.map authDkimForDomain cfg.domains);
signatureForDomain = domain: [
{
name = "${domain}_rsa";
value = {
inherit domain;
private-key = "%{file:${rsaKeyPath domain}}%";
selector = "snm_rsa";
headers = [
"From"
"To"
"Cc"
"Date"
"Subject"
"Message-ID"
"Organization"
"MIME-Version"
"Content-Type"
"In-Reply-To"
"References"
"List-Id"
"User-Agent"
"Thread-Topic"
"Thread-Index"
];
algorithm = "rsa-sha256";
canonicalization = "relaxed/relaxed";
report = true;
};
}
{
name = "${domain}_ed25519";
value = {
inherit domain;
private-key = "%{file:${ed25519KeyPath domain}}%";
selector = "snm_ed25519";
headers = [
"From"
"To"
"Cc"
"Date"
"Subject"
"Message-ID"
"Organization"
"MIME-Version"
"Content-Type"
"In-Reply-To"
"References"
"List-Id"
"User-Agent"
"Thread-Topic"
"Thread-Index"
];
algorithm = "ed25519-sha256";
canonicalization = "relaxed/relaxed";
report = true;
};
}
];
dkimSignatures = {
signature = lib.listToAttrs (lib.lists.flatten (lib.lists.map signatureForDomain cfg.domains));
};
dkimPublicKey =
tls_key:
''''${ replace(trimprefix(trimsuffix(${tls_key}.public_key_pem, ${publicKeyEnd}), ${publicKeyBegin}), "\n", "") }'';
in
{
options.khscodes.infrastructure.mailserver.dkim = {
vault = {
mount = lib.mkOption {
type = lib.types.str;
};
mountExpr = lib.mkOption {
type = lib.types.str;
default = "\${ vault_mount.${lib.khscodes.sanitize-terraform-name cfg.dkim.vault.mount}.path }";
};
prefixPath = lib.mkOption {
type = lib.types.str;
};
};
};
config = lib.mkIf (cfg.enable) {
khscodes.infrastructure.vault-server-approle.policy = {
"${cfg.dkim.vault.mount}/data/${cfg.dkim.vault.prefixPath}/*" = {
capabilities = [ "read" ];
};
};
khscodes.infrastructure.provisioning.pre.modules = [
(
{ ... }:
{
terraform.required_providers.tls = {
source = "hashicorp/tls";
version = "4.1.0";
};
provider.tls = { };
khscodes.cloudflare.enable = true;
resource.tls_private_key =
(lib.listToAttrs (
lib.lists.map (domain: {
name = "${lib.khscodes.sanitize-terraform-name domain}_dkim_rsa";
value = {
algorithm = "RSA";
rsa_bits = 2048;
};
}) cfg.domains
))
// (lib.listToAttrs (
lib.lists.map (domain: {
name = "${lib.khscodes.sanitize-terraform-name domain}_dkim_ed25519";
value = {
algorithm = "ED25519";
};
}) cfg.domains
));
khscodes.cloudflare.dns.txtRecords =
(lib.lists.map (domain: {
fqdn = "snm_rsa._domainkey.${domain}";
content = ''"''${ join("\" \"", regexall(".{1,255}", "v=DKIM1;k=rsa;p=${dkimPublicKey "tls_private_key.${lib.khscodes.sanitize-terraform-name domain}_dkim_rsa"}" )) }"'';
ttl = 600;
}) cfg.domains)
++ (lib.lists.map (domain: {
fqdn = "snm_ed25519._domainkey.${domain}";
content = ''"''${ join("\" \"", regexall(".{1,255}", "v=DKIM1;k=ed25519;p=${dkimPublicKey "tls_private_key.${lib.khscodes.sanitize-terraform-name domain}_dkim_ed25519"}" )) }"'';
ttl = 600;
}) cfg.domains);
resource.vault_kv_secret_v2 =
(lib.listToAttrs (
lib.lists.map (domain: {
name = "${lib.khscodes.sanitize-terraform-name domain}_dkim_rsa";
value = {
mount = cfg.dkim.vault.mountExpr;
name = cfg.dkim.vault.prefixPath + "/${domain}/rsa";
data_json = ''
{ "dkim_private_key": ''${ jsonencode(resource.tls_private_key.${lib.khscodes.sanitize-terraform-name domain}_dkim_rsa.private_key_pem) } }
'';
};
}) cfg.domains
))
// (lib.listToAttrs (
lib.lists.map (domain: {
name = "${lib.khscodes.sanitize-terraform-name domain}_dkim_ed25519";
value = {
mount = cfg.dkim.vault.mountExpr;
name = cfg.dkim.vault.prefixPath + "/${domain}/ed25519";
data_json = ''
{ "dkim_private_key": ''${ jsonencode(resource.tls_private_key.${lib.khscodes.sanitize-terraform-name domain}_dkim_ed25519.private_key_pem) } }
'';
};
}) cfg.domains
));
}
)
];
khscodes.services.vault-agent.templates = lib.lists.flatten (
lib.lists.map (domain: [
{
contents = ''
{{- with secret "${cfg.dkim.vault.mount}/data/${cfg.dkim.vault.prefixPath}/${domain}/rsa" -}}
{{ .Data.data.dkim_private_key }}
{{- end -}}
'';
destination = rsaKeyPath domain;
perms = "0600";
owner = "stalwart-mail";
group = "stalwart-mail";
restartUnits = [
"stalwart-mail.service"
];
}
{
contents = ''
{{- with secret "${cfg.dkim.vault.mount}/data/${cfg.dkim.vault.prefixPath}/${domain}/ed25519" -}}
{{ .Data.data.dkim_private_key }}
{{- end -}}
'';
destination = ed25519KeyPath domain;
perms = "0600";
owner = "stalwart-mail";
group = "stalwart-mail";
restartUnits = [
"stalwart-mail.service"
];
}
]) cfg.domains
);
services.stalwart-mail.settings = {
config.local-keys = [
"auth.*"
"signature.*"
];
auth.dkim = {
sign = authDkim ++ [
(otherwise false)
];
};
}
// dkimSignatures;
systemd.services.stalwart-mail = {
unitConfig.ConditionPathExists = keyFiles;
serviceConfig.ReadOnlyPaths = keyFiles;
};
};
}