Move the setup of the mailserver around
Some checks failed
/ check (push) Failing after 1m22s
/ dev-shell (push) Successful in 1m55s
/ rust-packages (push) Successful in 13m34s
/ systems (push) Successful in 54m5s
/ terraform-providers (push) Successful in 13m26s

Currently delivery of mails is broken. There's some work
to be done in accounts.nix. But once done this should (I think)
support all the use cases desired.
This commit is contained in:
Kaare Hoff Skovgaard 2025-07-31 00:04:13 +02:00
parent 02325a7017
commit fbe957b046
Signed by: khs
GPG key ID: C7D890804F01E9F0
20 changed files with 367 additions and 344 deletions

View file

@ -1,128 +0,0 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.khscodes.infrastructure.mailserver;
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.typs.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.types.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 ++ [ name ]
) [ ] cfg.accounts;
in
{
options.khscodes.infrastructure.mailserver.accounts = lib.mkOption {
type = lib.types.attrsOf accountOption;
default = { };
};
config = lib.mkIf cfg.enable {
};
}

View file

@ -1,9 +0,0 @@
{ lib, config, ... }:
let
cfg = config.khscodes.infrastructure.mailserver;
in
{
config = lib.mkIf cfg.enable {
khscodes.services.nginx.virtualHosts."${config.khscodes.networking.fqdn}" = { };
};
}

View file

@ -1,9 +0,0 @@
{ config, lib, ... }:
let
cfg = config.khscodes.infrastructure.mailserver;
in
{
config = lib.mkIf cfg.enable {
services.dovecot2.protocols = [ "sieve" ];
};
}

View file

@ -1,84 +1,46 @@
{
inputs,
config,
...
}:
{
imports = [
"${inputs.self}/nix/profiles/nixos/hetzner-server.nix"
# ./testuser.nix
./mailserver
];
khscodes.infrastructure.provisioning.pre.modules = [
(
{ ... }:
{
khscodes.vault = {
enable = true;
mount."mx.kaareskovgaard.net" = {
path = "mx.kaareskovgaard.net";
type = "kv";
options = {
version = "2";
khscodes = {
infrastructure = {
hetzner-instance = {
enable = true;
mapRdns = true;
server_type = "cax11";
};
provisioning.pre.modules = [
(
{ ... }:
{
khscodes.vault = {
enable = true;
mount."mx.kaareskovgaard.net" = {
path = "mx.kaareskovgaard.net";
type = "kv";
options = {
version = "2";
};
description = "Secrets used for mx.kaareskovgaard.net";
};
};
description = "Secrets used for mx.kaareskovgaard.net";
};
};
}
)
];
khscodes.infrastructure = {
kanidm-client-application = {
enable = true;
appName = "dovecot";
secretOwner = "dovecot2";
perms = "0644";
}
)
];
};
hetzner-instance = {
enable = true;
mapRdns = true;
server_type = "cax11";
};
mailserver = {
enable = true;
"mx.kaareskovgaard.net" = {
domains = [
"agerlin-skovgaard.dk"
"agerlinskovgaard.dk"
];
dkim = {
vault = {
mount = "mx.kaareskovgaard.net";
prefixPath = "dkim";
};
};
ldap = {
mount = "kanidm";
path = "ldap/dovecot";
};
accounts = import ./users.nix;
};
};
services.roundcube = {
enable = true;
hostName = "mail.kaareskovgaard.net";
configureNginx = true;
extraConfig = ''
# starttls needed for authentication, so the fqdn required to match
# the certificate
$config['smtp_host'] = "ssl://${config.khscodes.networking.fqdn}";
$config['imap_host'] = "ssl://${config.khscodes.networking.fqdn}";
$config['oauth_provider'] = 'generic';
$config['oauth_provider_name'] = 'Kanidm';
$config['oauth_client_id'] = 'dovecot';
$config['oauth_client_secret'] = file_get_contents("${config.khscodes.infrastructure.kanidm-client-application.secretFile}");
$config['oauth_auth_uri'] = 'https://login.kaareskovgaard.net/ui/oauth2';
$config['oauth_token_uri'] = 'https://login.kaareskovgaard.net/oauth2/token';
$config['oauth_identity_uri'] = 'https://login.kaareskovgaard.net/oauth2/openid/dovecot/userinfo';
$config['oauth_identity_fields'] = ['preferred_username'];
$config['oauth_scope'] = 'email openid profile';
$config['plugins'] = [
'managesieve',
];
'';
};
khscodes.services.nginx = {
enable = true;
virtualHosts."mail.kaareskovgaard.net" = { };

View file

@ -0,0 +1,244 @@
{
config,
lib,
pkgs,
...
}:
let
cfg = config.khscodes."mx.kaareskovgaard.net";
passDbFile = "/run/secret/dovecot/passwd";
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 ++ [ name ]
) [ ] 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";
# I would like to not use emails as usernames, but this is not something postfix is particularly happy with,
# as far as I can see. I *think* I can do this by randomly selecting an address, ie. the first, as the "primary"
# address, and then map all other addresses to that one. Then create a virtual mailbox map that maps that back
# to the username before delivering it to lmtp. All of this just worked when using ldap, but that meant
# not using code configured accounts set up with opentofu as well, and would also make the email delivery
# take a hard dependency on ldap (and not just the login process).
# Create extra virtual aliases for all accounts (as they are not emails, that maps them to their primary email).
# Then we map those emails back into the account name in the mailbox maps.
extraVirtualAliases = lib.mapAttrs (name: account: accountPrimaryEmail name account) cfg.accounts;
in
{
options.khscodes."mx.kaareskovgaard.net".accounts = lib.mkOption {
type = lib.types.attrsOf accountOption;
default = { };
};
config = {
mailserver = {
inherit extraVirtualAliases;
loginAccounts = lib.attrsets.mapAttrs (name: value: {
inherit (value)
name
aliasesRegexp
catchAll
quota
sieveScript
sendOnly
sendOnlyRejectMessage
;
aliases = accountAlternativeEmails name value;
hashedPasswordFile = bogusPasswdFile;
}) cfg.accounts;
};
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
'';
};
# This prevents local usernames without domain names to get rewritten.
# services.postfix.submissionOptions = submissionOptions;
# services.postfix.submissionsOptions = submissionOptions;
};
}

View file

@ -0,0 +1,6 @@
{ config, ... }:
{
config = {
khscodes.services.nginx.virtualHosts."${config.khscodes.networking.fqdn}" = { };
};
}

View file

@ -5,12 +5,11 @@
...
}:
let
cfg = config.khscodes.infrastructure.mailserver;
cfg = config.khscodes."mx.kaareskovgaard.net";
fqdn = config.khscodes.networking.fqdn;
in
{
options.khscodes.infrastructure.mailserver = {
enable = lib.mkEnableOption "Enables setting up stuff for a mail server";
options.khscodes."mx.kaareskovgaard.net" = {
domains = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
@ -18,6 +17,7 @@ in
};
imports = [
inputs.simple-nixos-mailserver.nixosModules.mailserver
./accounts.nix
./acme.nix
./dmarc.nix
./dane.nix
@ -29,9 +29,9 @@ in
./prometheus.nix
./openid-connect.nix
./ldap.nix
./roundcube.nix
];
config = lib.mkIf cfg.enable {
# TODO: Include a similiar rule for openstack
config = {
khscodes.infrastructure.hetzner-instance.extraFirewallRules = [
{
direction = "out";
@ -80,6 +80,7 @@ in
)
];
mailserver = {
inherit (cfg) domains;
enable = true;
enableImap = false;
enableImapSsl = true;
@ -87,7 +88,6 @@ in
enableSubmissionSsl = true;
fqdn = config.khscodes.networking.fqdn;
useUTF8FolderNames = true;
domains = cfg.domains;
certificateScheme = "acme";
};
services.fail2ban.jails = {

View file

@ -5,7 +5,10 @@
...
}:
let
cfg = config.khscodes.infrastructure.mailserver;
cfg = config.khscodes."mx.kaareskovgaard.net";
mount = "mx.kaareskovgaard.net";
mountExpr = "\${ vault_mount.${lib.khscodes.sanitize-terraform-name mount}.path }";
prefixPath = "dkim";
opensslStripAsn1 = pkgs.writeTextFile {
name = "openssl-strip-asn1";
executable = true;
@ -48,23 +51,9 @@ let
''''${ 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) {
config = {
khscodes.infrastructure.vault-server-approle.policy = {
"${cfg.dkim.vault.mount}/data/${cfg.dkim.vault.prefixPath}/*" = {
"${mount}/data/${prefixPath}/*" = {
capabilities = [ "read" ];
};
};
@ -142,8 +131,8 @@ in
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";
mount = mountExpr;
name = prefixPath + "/${domain}/rsa";
data_json = ''
{ "dkim_private_key": ''${ jsonencode(resource.tls_private_key.${lib.khscodes.sanitize-terraform-name domain}_dkim_rsa.private_key_pem) } }
'';
@ -154,8 +143,8 @@ in
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";
mount = mountExpr;
name = prefixPath + "/${domain}/ed25519";
data_json = ''
{ "dkim_private_key": ''${ jsonencode(resource.tls_private_key.${lib.khscodes.sanitize-terraform-name domain}_dkim_ed25519.private_key_pem) } }
'';
@ -169,7 +158,7 @@ in
lib.lists.map (domain: [
{
contents = ''
{{- with secret "${cfg.dkim.vault.mount}/data/${cfg.dkim.vault.prefixPath}/${domain}/rsa" -}}
{{- with secret "${mount}/data/${prefixPath}/${domain}/rsa" -}}
{{ .Data.data.dkim_private_key }}
{{- end -}}
'';
@ -185,7 +174,7 @@ in
# rspamd does not like reading ed25519 keys with begin/end pairs, as they get parsed as
# rsa keys
contents = ''
{{- with secret "${cfg.dkim.vault.mount}/data/${cfg.dkim.vault.prefixPath}/${domain}/ed25519" -}}
{{- with secret "${mount}/data/${prefixPath}/${domain}/ed25519" -}}
{{ .Data.data.dkim_private_key }}
{{- end -}}
'';

View file

@ -1,9 +1,9 @@
{ config, lib, ... }:
let
cfg = config.khscodes.infrastructure.mailserver;
cfg = config.khscodes."mx.kaareskovgaard.net";
in
{
config = lib.mkIf cfg.enable {
config = {
khscodes.infrastructure.provisioning.pre.modules = [
{
khscodes.cloudflare.dns.txtRecords = lib.lists.map (domain: {

View file

@ -1,26 +1,14 @@
{
lib,
config,
pkgs,
...
}:
let
cfg = config.khscodes.infrastructure.mailserver;
ldapMount = "kanidm";
ldapPath = "ldap/dovecot";
secretFile = "/run/secret/dovecot/dovecot-ldap.conf.ext";
in
{
options.khscodes.infrastructure.mailserver = {
ldap = {
mount = lib.mkOption {
type = lib.types.str;
};
path = lib.mkOption {
type = lib.types.str;
};
};
};
config = lib.mkIf cfg.enable {
config = {
services.dovecot2.extraConfig = ''
passdb {
driver = ldap
@ -41,7 +29,7 @@ in
khscodes.services.vault-agent.templates = [
{
contents = ''
{{- with secret "${cfg.ldap.mount}/data/${cfg.ldap.path}" -}}
{{- with secret "${ldapMount}/data/${ldapPath}" -}}
ldap_version = 3
uris = ldaps://login.kaareskovgaard.net
@ -78,7 +66,7 @@ in
}
];
khscodes.infrastructure.vault-server-approle.policy = {
"${cfg.ldap.mount}/data/${cfg.ldap.path}" = {
"${ldapMount}/data/${ldapPath}" = {
capabilities = [ "read" ];
};
};

View file

@ -0,0 +1,10 @@
{
config = {
services.dovecot2.protocols = [ "sieve" ];
services.roundcube.extraConfig = ''
$config['plugins'] = [
'managesieve',
];
'';
};
}

View file

@ -6,7 +6,7 @@
}:
let
fqdn = config.khscodes.networking.fqdn;
cfg = config.khscodes.infrastructure.mailserver;
cfg = config.khscodes."mx.kaareskovgaard.net";
# Increment this if ever changing mta-sts settings.
policyVersion = 2;
mtaStsWellKnown = pkgs.writeTextFile {
@ -20,7 +20,7 @@ let
};
in
{
config = lib.mkIf cfg.enable {
config = {
khscodes.services.nginx.virtualHosts = (
lib.listToAttrs (
lib.lists.map (domain: {

View file

@ -1,10 +1,15 @@
{ config, lib, ... }:
{ config, ... }:
let
cfg = config.khscodes.infrastructure.mailserver;
oauthConfigFile = "/run/secret/dovecot/dovecot-oauth2.conf.ext";
in
{
config = lib.mkIf cfg.enable {
config = {
khscodes.infrastructure.kanidm-client-application = {
enable = true;
appName = "dovecot";
secretOwner = "root";
perms = "0644";
};
khscodes.services.vault-agent.templates = [
{
contents = ''
@ -23,6 +28,17 @@ in
restartUnits = [ "dovecot2.service" ];
}
];
services.roundcube.extraConfig = ''
$config['oauth_provider'] = 'generic';
$config['oauth_provider_name'] = 'Kanidm';
$config['oauth_client_id'] = 'dovecot';
$config['oauth_client_secret'] = file_get_contents("${config.khscodes.infrastructure.kanidm-client-application.secretFile}");
$config['oauth_auth_uri'] = 'https://login.kaareskovgaard.net/ui/oauth2';
$config['oauth_token_uri'] = 'https://login.kaareskovgaard.net/oauth2/token';
$config['oauth_identity_uri'] = 'https://login.kaareskovgaard.net/oauth2/openid/dovecot/userinfo';
$config['oauth_identity_fields'] = ['preferred_username'];
$config['oauth_scope'] = 'email openid profile';
'';
services.dovecot2.extraConfig = ''
auth_mechanisms = $auth_mechanisms oauthbearer xoauth2

View file

@ -1,9 +1,5 @@
{ config, lib, ... }:
let
cfg = config.khscodes.infrastructure.mailserver;
in
{
config = lib.mkIf cfg.enable {
config = {
services.prometheus.exporters.postfix = {
enable = true;

View file

@ -0,0 +1,18 @@
{ config, ... }:
{
services.roundcube = {
enable = true;
hostName = "mail.kaareskovgaard.net";
configureNginx = true;
extraConfig = ''
# starttls needed for authentication, so the fqdn required to match
# the certificate
$config['smtp_host'] = "ssl://${config.khscodes.networking.fqdn}";
$config['imap_host'] = "ssl://${config.khscodes.networking.fqdn}";
'';
};
khscodes.services.nginx = {
enable = true;
virtualHosts."mail.kaareskovgaard.net" = { };
};
}

View file

@ -1,9 +1,9 @@
{ config, lib, ... }:
let
cfg = config.khscodes.infrastructure.mailserver;
cfg = config.khscodes."mx.kaareskovgaard.net";
in
{
config = lib.mkIf cfg.enable {
config = {
khscodes.infrastructure.provisioning.pre.modules = [
{
khscodes.cloudflare.dns.txtRecords = lib.lists.map (domain: {

View file

@ -1,9 +1,9 @@
{ config, lib, ... }:
let
cfg = config.khscodes.infrastructure.mailserver;
cfg = config.khscodes."mx.kaareskovgaard.net";
in
{
config = lib.mkIf cfg.enable {
config = {
khscodes.infrastructure.provisioning.pre.modules = [
{
khscodes.cloudflare.dns.txtRecords = lib.lists.map (domain: {

View file

@ -1,71 +0,0 @@
{
khscodes.infrastructure.vault-server-approle.policy = {
"mail.kaareskovgaard.net/data/users/*" = {
capabilities = [ "read" ];
};
};
khscodes.services.vault-agent.templates = [
{
contents = ''
{{- with secret "mail.kaareskovgaard.net/data/users/test" -}}
{{ .Data.data.hashed_password }}
{{- end -}}
'';
destination = "/run/secret/mailserver/users/test.passwd.hash";
perms = "0600";
owner = "rspamd";
group = "rspamd";
restartUnits = [
"rspamd.service"
"postfix.service"
];
}
];
systemd.services.postfix = {
unitConfig = {
ConditionPathExists = [ "/run/secret/mailserver/users/test.passwd.hash" ];
};
};
systemd.services.rspamd = {
unitConfig = {
ConditionPathExists = [ "/run/secret/mailserver/users/test.passwd.hash" ];
};
};
khscodes.infrastructure.provisioning.pre.modules = [
(
{ config, ... }:
{
terraform.required_providers.random = {
source = "hashicorp/random";
version = "3.7.2";
};
provider.random = { };
resource.random_password.test = {
length = 48;
numeric = true;
lower = true;
upper = true;
special = false;
};
resource.vault_kv_secret_v2.test = {
mount = config.khscodes.vault.output.mount."mail.kaareskovgaard.net".path;
name = "users/test";
data_json = ''
{
"hashed_password": ''${ jsonencode(resource.random_password.test.bcrypt_hash) },
"password": ''${ jsonencode(resource.random_password.test.result) }
}
'';
};
}
)
];
mailserver.loginAccounts = {
"test@agerlin-skovgaard.dk" = {
hashedPasswordFile = "/run/secret/mailserver/users/test.passwd.hash";
};
};
}

View file

@ -0,0 +1,11 @@
{
"khs" = {
isLdapAccount = true;
name = "Kaare Agerlin Skovgaard";
aliases = [
"kaare@agerlin-skovgaard.dk"
"kaare@agerlinskovgaard.dk"
];
quota = "10G";
};
}