268 lines
8.1 KiB
Nix
268 lines
8.1 KiB
Nix
{
|
|
config,
|
|
lib,
|
|
pkgs,
|
|
...
|
|
}:
|
|
let
|
|
cfg = config.khscodes."mx.kaareskovgaard.net";
|
|
passDbFile = "/run/secret/dovecot/passwd";
|
|
# This just replicates what simple-nixos-mailserver does, but using the proper usernames
|
|
userDbFile = pkgs.writeTextFile {
|
|
name = "userdb";
|
|
text = lib.concatStringsSep "\n" (
|
|
lib.mapAttrsToList (
|
|
name: value:
|
|
"${name}:::::::"
|
|
+ lib.optionalString (value.quota != null) "userdb_quota_rule=*:storage=${value.quota}"
|
|
) cfg.accounts
|
|
);
|
|
};
|
|
bogusPasswdFile = pkgs.writeTextFile {
|
|
name = "bogus-passwd";
|
|
text = "$6$1234";
|
|
};
|
|
accountPrimaryEmail =
|
|
name: account: if account.isLdapAccount then lib.lists.head account.aliases else name;
|
|
accountAlternativeEmails =
|
|
name: account:
|
|
if account.isLdapAccount then
|
|
lib.lists.ifilter0 (idx: _: idx > 0) account.aliases
|
|
else
|
|
account.aliases;
|
|
accountOption = lib.khscodes.mkSubmodule {
|
|
description = "mail account";
|
|
options = {
|
|
name = lib.mkOption {
|
|
type = lib.types.str;
|
|
example = "user1@example.com";
|
|
description = "Username";
|
|
};
|
|
|
|
aliases = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
example = [
|
|
"abuse@example.com"
|
|
"postmaster@example.com"
|
|
];
|
|
default = [ ];
|
|
description = ''
|
|
A list of aliases of this login account.
|
|
Note: Use list entries like "@example.com" to create a catchAll
|
|
that allows sending from all email addresses in these domain.
|
|
'';
|
|
};
|
|
|
|
aliasesRegexp = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
example = [ ''/^tom\..*@domain\.com$/'' ];
|
|
default = [ ];
|
|
description = ''
|
|
Same as {option}`mailserver.aliases` but using PCRE (Perl compatible regex).
|
|
'';
|
|
};
|
|
|
|
catchAll = lib.mkOption {
|
|
type = lib.types.listOf (lib.types.enum config.mailserver.domains);
|
|
example = [
|
|
"example.com"
|
|
"example2.com"
|
|
];
|
|
default = [ ];
|
|
description = ''
|
|
For which domains should this account act as a catch all?
|
|
Note: Does not allow sending from all addresses of these domains.
|
|
'';
|
|
};
|
|
|
|
quota = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
example = "2G";
|
|
description = ''
|
|
Per user quota rules. Accepted sizes are `xx k/M/G/T` with the
|
|
obvious meaning. Leave blank for the standard quota `100G`.
|
|
'';
|
|
};
|
|
|
|
sieveScript = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.lines;
|
|
default = null;
|
|
example = ''
|
|
require ["fileinto", "mailbox"];
|
|
|
|
if address :is "from" "gitlab@mg.gitlab.com" {
|
|
fileinto :create "GitLab";
|
|
stop;
|
|
}
|
|
|
|
# This must be the last rule, it will check if list-id is set, and
|
|
# file the message into the Lists folder for further investigation
|
|
elsif header :matches "list-id" "<?*>" {
|
|
fileinto :create "Lists";
|
|
stop;
|
|
}
|
|
'';
|
|
description = ''
|
|
Per-user sieve script.
|
|
'';
|
|
};
|
|
|
|
sendOnly = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = ''
|
|
Specifies if the account should be a send-only account.
|
|
Emails sent to send-only accounts will be rejected from
|
|
unauthorized senders with the `sendOnlyRejectMessage`
|
|
stating the reason.
|
|
'';
|
|
};
|
|
|
|
sendOnlyRejectMessage = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "This account cannot receive emails.";
|
|
description = ''
|
|
The message that will be returned to the sender when an email is
|
|
sent to a send-only account. Only used if the account is marked
|
|
as send-only.
|
|
'';
|
|
};
|
|
|
|
isLdapAccount = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = false;
|
|
description = "Marks the account as an LDAP account. Non LDAP accounts gets passwords provisioned, such that other services can use them";
|
|
};
|
|
};
|
|
};
|
|
|
|
systemAccounts = lib.attrsets.foldlAttrs (
|
|
acc: name: value:
|
|
if value.isLdapAccount then acc else acc ++ [ (accountPrimaryEmail name value) ]
|
|
) [ ] cfg.accounts;
|
|
|
|
systemAccountsPassDbTemplateContents =
|
|
lib.concatStringsSep "\n" (
|
|
lib.lists.map (account: ''
|
|
{{- with secret "mx.kaareskovgaard.net/data/users/${account}" -}}
|
|
"${account}:{{ .Data.data.hashed_password }}::::::"
|
|
{{- end -}}
|
|
'') systemAccounts
|
|
)
|
|
# Just make sure the file is not empty
|
|
+ "\n";
|
|
|
|
data = import ./accounts/mailbox_map.nix {
|
|
inherit lib accountPrimaryEmail accountAlternativeEmails;
|
|
accounts = cfg.accounts;
|
|
extraVirtualAliases = { };
|
|
};
|
|
|
|
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
|
|
mappedRegexFile = name: "pcre:/var/lib/postfix/conf/${name}";
|
|
in
|
|
{
|
|
options.khscodes."mx.kaareskovgaard.net".accounts = lib.mkOption {
|
|
type = lib.types.attrsOf accountOption;
|
|
default = { };
|
|
};
|
|
config = {
|
|
mailserver = {
|
|
loginAccounts = lib.attrsets.mapAttrs' (name: value: {
|
|
name = accountPrimaryEmail name value;
|
|
value = {
|
|
inherit (value)
|
|
name
|
|
aliasesRegexp
|
|
catchAll
|
|
quota
|
|
sieveScript
|
|
sendOnly
|
|
sendOnlyRejectMessage
|
|
;
|
|
aliases = accountAlternativeEmails name value;
|
|
hashedPasswordFile = bogusPasswdFile;
|
|
};
|
|
}) cfg.accounts;
|
|
extraVirtualAliases = data.mailserverExtraVirtualAliases;
|
|
};
|
|
services.postfix = {
|
|
mapFiles."valias_maps" = data.valiases_file;
|
|
mapFiles."regex_valias_maps" = data.regex_valiases_file;
|
|
mapFiles."vaccounts" = lib.mkForce data.vaccounts_file;
|
|
mapFiles."regex_vaccounts" = lib.mkForce data.regex_vaccounts_file;
|
|
config.virtual_mailbox_maps = lib.mkForce [
|
|
(mappedFile "valias_maps")
|
|
(mappedRegexFile "regex_valias_maps")
|
|
];
|
|
|
|
};
|
|
khscodes.infrastructure.vault-server-approle.policy = {
|
|
"mx.kaareskovgaard.net/data/users/*" = {
|
|
capabilities = [ "read" ];
|
|
};
|
|
};
|
|
khscodes.infrastructure.provisioning.pre.modules = [
|
|
{
|
|
terraform.required_providers.random = {
|
|
source = "hashicorp/random";
|
|
version = "3.7.2";
|
|
};
|
|
provider.random = { };
|
|
}
|
|
]
|
|
++ (lib.lists.map (
|
|
account:
|
|
let
|
|
tfName = lib.khscodes.sanitize-terraform-name account;
|
|
in
|
|
{
|
|
resource.random_password."${tfName}" = {
|
|
length = 48;
|
|
numeric = true;
|
|
lower = true;
|
|
upper = true;
|
|
special = false;
|
|
};
|
|
|
|
resource.vault_kv_secret_v2."${tfName}" = {
|
|
mount = config.khscodes.vault.output.mount."mx.kaareskovgaard.net".path;
|
|
name = "users/${account}";
|
|
data_json = ''
|
|
{
|
|
"hashed_password": ''${ jsonencode(resource.random_password.${tfName}.bcrypt_hash) },
|
|
"password": ''${ jsonencode(resource.random_password.${tfName}.result) }
|
|
}
|
|
'';
|
|
};
|
|
}
|
|
) systemAccounts);
|
|
khscodes.services.vault-agent.templates = [
|
|
{
|
|
contents = systemAccountsPassDbTemplateContents;
|
|
destination = passDbFile;
|
|
perms = "0600";
|
|
owner = "dovecot2";
|
|
group = "dovecot2";
|
|
restartUnits = [
|
|
"dovecot2.service"
|
|
];
|
|
}
|
|
];
|
|
systemd.services.dovecot2 = {
|
|
unitConfig.ConditionPathExists = [ passDbFile ];
|
|
serviceConfig.ReadOnlyPaths = [ passDbFile ];
|
|
# simple-nixos-mailserver creates its own passwd file,
|
|
# but the passwords in that file are all bogus, so replace them
|
|
# with our own.
|
|
preStart = lib.mkAfter ''
|
|
cp ${passDbFile} /run/dovecot2/passwd
|
|
cp ${userDbFile} /run/dovecot2/userdb
|
|
'';
|
|
};
|
|
# This prevents local usernames without domain names to get rewritten.
|
|
# services.postfix.submissionOptions = submissionOptions;
|
|
# services.postfix.submissionsOptions = submissionOptions;
|
|
};
|
|
}
|