Move the setup of the mailserver around
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:
parent
02325a7017
commit
fbe957b046
20 changed files with 367 additions and 344 deletions
|
@ -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 {
|
||||
};
|
||||
}
|
|
@ -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}" = { };
|
||||
};
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
{ config, lib, ... }:
|
||||
let
|
||||
cfg = config.khscodes.infrastructure.mailserver;
|
||||
in
|
||||
{
|
||||
config = lib.mkIf cfg.enable {
|
||||
services.dovecot2.protocols = [ "sieve" ];
|
||||
};
|
||||
}
|
|
@ -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" = { };
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
{ config, ... }:
|
||||
{
|
||||
config = {
|
||||
khscodes.services.nginx.virtualHosts."${config.khscodes.networking.fqdn}" = { };
|
||||
};
|
||||
}
|
|
@ -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 = {
|
|
@ -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 -}}
|
||||
'';
|
|
@ -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: {
|
|
@ -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" ];
|
||||
};
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
config = {
|
||||
services.dovecot2.protocols = [ "sieve" ];
|
||||
services.roundcube.extraConfig = ''
|
||||
$config['plugins'] = [
|
||||
'managesieve',
|
||||
];
|
||||
'';
|
||||
};
|
||||
}
|
|
@ -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: {
|
|
@ -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
|
||||
|
|
@ -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;
|
|
@ -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" = { };
|
||||
};
|
||||
}
|
|
@ -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: {
|
|
@ -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: {
|
|
@ -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";
|
||||
};
|
||||
};
|
||||
}
|
11
nix/systems/aarch64-linux/mx.kaareskovgaard.net/users.nix
Normal file
11
nix/systems/aarch64-linux/mx.kaareskovgaard.net/users.nix
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"khs" = {
|
||||
isLdapAccount = true;
|
||||
name = "Kaare Agerlin Skovgaard";
|
||||
aliases = [
|
||||
"kaare@agerlin-skovgaard.dk"
|
||||
"kaare@agerlinskovgaard.dk"
|
||||
];
|
||||
quota = "10G";
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue