Compare commits

...

2 commits

Author SHA1 Message Date
fbe957b046
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.
2025-07-31 00:04:13 +02:00
02325a7017
Begin preparing to move LDAP accounts into passdb only
This should allow LDAP accounts to have password
set in LDAP, as well as provisioning service accounts
statically in nix.

This will also move alias configuration of all accounts
into nix as well.
2025-07-30 21:36:48 +02:00
20 changed files with 436 additions and 295 deletions

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,96 +0,0 @@
{ lib, config, ... }:
let
cfg = config.khscodes.infrastructure.mailserver;
secretFile = "/run/secret/dovecot/ldap";
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 {
mailserver.ldap = {
enable = true;
uris = [ "ldaps://login.kaareskovgaard.net" ];
searchBase = "dc=login,dc=kaareskovgaard,dc=net";
searchScope = "sub";
bind = {
dn = "dn=token";
passwordFile = secretFile;
};
dovecot = {
# Map LDAP uid to dovecot user, and ldap userPassword to dovecot password
passAttrs = "uid=user";
passFilter = "(&(class=account)(memberOf=mail_user)(uid=%u))";
# This filter is used both when receiving mail (thus needing to lookup by mail address, and when authenticating, requriing the lookup by uid.)
# Note that the pass filter only allows looking up by uid, so should still only be able to authenticate using that.
userFilter = "(&(class=account)(memberOf=mail_user)(|(mail=%u)(uid=%u)))";
userAttrs = "uid=user";
};
postfix = {
filter = "(&(class=account)(memberOf=mail_user)(mail=%s))";
mailAttribute = "uid";
uidAttribute = "uid";
};
};
systemd.services = {
dovecot2 = {
unitConfig = {
ConditionPathExists = [ secretFile ];
};
};
postfix = {
unitConfig = {
ConditionPathExists = [ secretFile ];
};
};
postfix-setup = {
unitConfig = {
ConditionPathExists = [ secretFile ];
};
};
};
khscodes.services.vault-agent.templates = [
{
contents = ''
{{- with secret "${cfg.ldap.mount}/data/${cfg.ldap.path}" -}}
{{ .Data.data.apiToken }}
{{- end -}}
'';
destination = secretFile;
owner = "dovecot";
group = "dovecot";
restartUnits = [
"dovecot2.service"
"postfix.service"
];
}
];
# TODO: Include a similiar rule for openstack
khscodes.infrastructure.hetzner-instance.extraFirewallRules = [
{
direction = "out";
protocol = "tcp";
port = 636;
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "ldaps";
}
];
khscodes.infrastructure.vault-server-approle.policy = {
"${cfg.ldap.mount}/data/${cfg.ldap.path}" = {
capabilities = [ "read" ];
};
};
};
}

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,14 +1,20 @@
{
inputs,
config,
...
}:
{
imports = [
"${inputs.self}/nix/profiles/nixos/hetzner-server.nix"
# ./testuser.nix
./mailserver
];
khscodes.infrastructure.provisioning.pre.modules = [
khscodes = {
infrastructure = {
hetzner-instance = {
enable = true;
mapRdns = true;
server_type = "cax11";
};
provisioning.pre.modules = [
(
{ ... }:
{
@ -26,59 +32,15 @@
}
)
];
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";
accounts = import ./users.nix;
};
};
ldap = {
mount = "kanidm";
path = "ldap/dovecot";
};
};
};
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

@ -0,0 +1,74 @@
{
pkgs,
...
}:
let
ldapMount = "kanidm";
ldapPath = "ldap/dovecot";
secretFile = "/run/secret/dovecot/dovecot-ldap.conf.ext";
in
{
config = {
services.dovecot2.extraConfig = ''
passdb {
driver = ldap
args = ${secretFile}
}
'';
systemd.services = {
dovecot2 = {
unitConfig = {
ConditionPathExists = [ secretFile ];
};
serviceConfig.ReadOnlyPaths = [
secretFile
];
};
};
khscodes.services.vault-agent.templates = [
{
contents = ''
{{- with secret "${ldapMount}/data/${ldapPath}" -}}
ldap_version = 3
uris = ldaps://login.kaareskovgaard.net
tls_require_cert = hard
tls_ca_cert_file = ${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt
dn = dn=token
dnpass = {{ .Data.data.apiToken }}
auth_bind = yes
base = dc=login,dc=kaareskovgaard,dc=net
scope = subtree
pass_attrs = uid=user
pass_filter = (&(class=account)(memberOf=mail_user)(uid=%u)
{{- end -}}
'';
destination = secretFile;
owner = "root";
group = "root";
restartUnits = [
"dovecot2.service"
];
}
];
# TODO: Include a similiar rule for openstack
khscodes.infrastructure.hetzner-instance.extraFirewallRules = [
{
direction = "out";
protocol = "tcp";
port = 636;
destination_ips = [
"0.0.0.0/0"
"::/0"
];
description = "ldaps";
}
];
khscodes.infrastructure.vault-server-approle.policy = {
"${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";
};
}