Compare commits
2 commits
cc1ab841c2
...
fbe957b046
Author | SHA1 | Date | |
---|---|---|---|
fbe957b046 | |||
02325a7017 |
20 changed files with 436 additions and 295 deletions
|
@ -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,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" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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: {
|
|
@ -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" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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