mail-server/mail-server.nix

414 lines
13 KiB
Nix
Raw Normal View History

2023-09-23 15:20:13 -07:00
{ config, lib, pkgs, ... }@toplevel:
2023-09-17 09:57:55 -07:00
2023-09-17 23:18:07 -07:00
with lib;
2023-09-23 16:12:05 -07:00
let
cfg = config.fudo.mail;
hostname = config.instance.hostname;
2023-09-23 16:13:57 -07:00
hostSecrets = config.fudo.secrets.host-secrets."${hostname}";
2023-09-23 15:20:13 -07:00
2023-09-17 23:18:07 -07:00
in {
2023-09-17 09:57:55 -07:00
options.fudo.mail = with types; {
enable = mkEnableOption "Enable mail server.";
state-directory = mkOption {
type = str;
description = "Directory at which to store server state.";
};
primary-domain = mkOption {
type = str;
description = "Primary domain name served by this server.";
};
extra-domains = mkOption {
type = listOf str;
description = "List of additional domains served by this server.";
default = [ ];
};
2023-09-23 15:20:13 -07:00
ldap = {
authentik-host = mkOption {
type = str;
description = "Hostname of the LDAP outpost provider.";
default = "authentik.${toplevel.config.fudo.mail.primary-domain}";
};
outpost-token = mkOption {
type = str;
description = "Token with which to authenticate to the Authentik host.";
};
# bind-dn = mkOption {
# type = str;
# description = "DN as which to bind with the LDAP server.";
# };
# bind-password-file = mkOption {
# type = str;
# description =
# "File containing password with which to bind with the LDAP server.";
# };
base = mkOption {
type = str;
description = "Base of the LDAP server.";
example = "dc=fudo,dc=org";
};
member-ou = mkOption {
type = str;
description = "Organizational unit containing users.";
default = "ou=members";
};
};
images.ldap-proxy = mkOption {
type = str;
description = "Docker image to use for LDAP proxy.";
default = "ghcr.io/goauthentik/ldap";
};
2023-09-17 09:57:55 -07:00
smtp = {
hostname = mkOption {
type = str;
description =
"Hostname too use for the SMTP server. Must resolve to this host.";
default = "smtp.${config.fudo.mail.primary-domain}";
};
2023-09-23 15:20:13 -07:00
ssl-directory = mkOption {
type = str;
description =
"Directory containing SSL certificates for SMTP hostname.";
2023-09-17 09:57:55 -07:00
};
};
imap = {
hostname = mkOption {
type = str;
description =
"Hostname too use for the IMAP server. Must resolve to this host.";
default = "imap.${config.fudo.mail.primary-domain}";
};
2023-09-23 15:20:13 -07:00
ssl-directory = mkOption {
type = str;
description =
"Directory containing SSL certificates for IMAP hostname.";
2023-09-17 09:57:55 -07:00
};
};
};
2023-09-17 23:24:13 -07:00
config = mkIf cfg.enable {
2023-09-17 09:57:55 -07:00
services.nginx = {
virtualHosts = {
"${cfg.smtp.hostname}".locations."/metrics" = {
proxyPass = "http://localhost:${metricsPort}/metrics";
};
"${cfg.imap.hostname}".locations."/metrics" = {
proxyPass = "http://localhost:${metricsPort}/metrics";
};
};
};
2023-09-21 18:01:44 -07:00
fudo.secrets.host-secrets."${hostname}" = {
2023-09-23 15:20:13 -07:00
mailLdapProxyEnv = {
source-file = pkgs.writeText "ldap-proxy.env" ''
AUTHENTIK_HOST=${cfg.ldap.authentik-host}
AUTHENTIK_TOKEN=${cfg.ldap.outpost-token}
AUTHENTIK_INSECURE=false
'';
target-file = "/run/ldap-proxy/env";
};
2023-09-21 18:01:44 -07:00
dovecotLdapConfig = {
source-file = pkgs.writeText "dovecot-ldap.conf"
(concatStringsSep "\n" [
2023-09-23 15:20:13 -07:00
"uris = ldap://ldap-proxy:3389"
2023-09-21 18:01:44 -07:00
"ldap_version = 3"
2023-09-23 15:20:13 -07:00
# "dn = ${cfg.ldap.bind-dn}"
# "dnpass = ${readFile cfg.ldap.bind-password-file}"
2023-09-21 18:01:44 -07:00
"auth_bind = yes"
"auth_bind_userdn = uid=%u,${cfg.ldap.member-ou},${cfg.ldap.base}"
"base = ${cfg.ldap.base}"
]);
target-file = "/run/dovecot-secret/ldap.conf";
};
};
2023-09-23 16:17:35 -07:00
users = {
users = {
mailserver-dovecot = {
uid = 4455;
isSystemUser = true;
group = "mailserver-dovecot";
};
mailserver-antivirus = {
uid = 4456;
isSystemUser = true;
group = "mailserver-antivirus";
};
mailserver-dkim = {
uid = 4457;
isSystemUser = true;
group = "mailserver-dkim";
};
2023-09-21 18:01:44 -07:00
};
2023-09-23 16:17:35 -07:00
groups = {
mailserver-dovecot = { };
mailserver-antivirus = { };
mailserver-dkim = { };
2023-09-21 18:01:44 -07:00
};
};
systemd.tmpfiles.rules = [
"d ${cfg.state-directory}/dovecot 0700 mailserver-dovecot - - -"
"d ${cfg.state-directory}/antivirus 0700 mailserver-antivirus - - -"
"d ${cfg.state-directory}/dkim 0700 mailserver-dkim - - -"
];
2023-09-17 09:57:55 -07:00
virtualisation.arion.projects.mail-server.settings = let
image = { pkgs, ... }: {
project.name = "fudo-mailserver";
networks = {
external_network.internal = false;
internal_network.internal = true;
};
serices = let
antivirusPort = 15407;
antispamPort = 11335;
lmtpPort = 24;
authPort = 5447;
userdbPort = 5448;
metricsPort = 5034;
2023-09-21 18:01:44 -07:00
mkUserMap = username:
let uid = config.users.users."${username}".uid;
in "${uid}:${uid}";
2023-09-17 09:57:55 -07:00
in {
smtp = {
networks = [
"internal_network"
# Needs access to internet to forward emails
"external_network"
];
2023-09-21 18:01:44 -07:00
volumes = [
"${hostSecrets.dovecotLdapConfig.target-file}:/run/dovecot2/conf.d/ldap.conf:ro"
2023-09-23 15:20:13 -07:00
"${cfg.smtp.ssl-directory}:/run/certs/smtp"
2023-09-21 18:01:44 -07:00
];
2023-09-17 09:57:55 -07:00
ports = [ "25:25" "587:587" "465:465" "2525:2525" ];
nixos = {
useSystemd = true;
configuration = [
(import ./postfix.nix)
2023-09-21 18:01:44 -07:00
(import ./dovecot.nix)
2023-09-17 09:57:55 -07:00
{
boot.tmpOnTmpfs = true;
system.nssModules = lib.mkForce [ ];
2023-09-21 18:01:44 -07:00
2023-09-17 09:57:55 -07:00
fudo.mail.postfix = {
enable = true;
debug = cfg.debug;
domain = cfg.primary-domain;
local-domains = cfg.extra-domains;
hostname = cfg.smtp.hostname;
trusted-networks = cfg.trusted-networks;
blacklist = {
senders = cfg.blacklist.senders;
recipients = cfg.blacklist.recipients;
dns = cfg.blacklist.dns;
};
aliases = {
user-aliases = cfg.user-aliases;
alias-users = cfg.alias-users;
};
ssl = {
2023-09-23 15:20:13 -07:00
certificate =
"/run/certs/smtp/fullchain.pem"; # FIXME: or just cert?
private-key = "/run/certs/smtp/key.pem";
2023-09-17 09:57:55 -07:00
};
sasl-domain = cfg.sasl-domain;
message-size-limit = cfg.message-size-limit;
ports = { metrics = metricsPort; };
2023-09-21 18:01:44 -07:00
rspamd-server = {
host = "antispam";
port = antispamPort;
};
lmtp-server = {
host = "imap";
port = lmtpPort;
};
dkim-server = {
host = "dkim";
port = dkimPort;
};
ldap-conf = "/run/dovecot2/conf.d/ldap.conf";
2023-09-17 09:57:55 -07:00
};
}
];
};
};
imap = {
networks = [ "internal_network" ];
ports = [ "143:143" "993:993" ];
2023-09-21 18:01:44 -07:00
user = mkUserMap "mailserver-dovecot";
volumes = [
"${cfg.state-directory}/dovecot:/state"
"${hostSecrets.dovecotLdapConfig.target-file}:/run/dovecot2/conf.d/ldap.conf:ro"
2023-09-23 15:20:13 -07:00
"${cfg.imap.ssl-directory}:/run/certs/imap"
2023-09-21 18:01:44 -07:00
];
2023-09-17 09:57:55 -07:00
nixos = {
useSystemd = true;
configuration = [
(import ./dovecot.nix)
{
boot.tmpOnTmpfs = true;
system.nssModules = lib.mkForce [ ];
fudo.mail.dovecot = {
enable = true;
debug = cfg.debug;
2023-09-21 18:01:44 -07:00
state-directory = "/state";
2023-09-17 09:57:55 -07:00
ports = {
lmtp = lmtpPort;
auth = authPort;
userdb = userdbPort;
metrics = metricsPort;
};
mail-user = cfg.mail-user;
mail-group = cfg.mail-group;
ssl = {
2023-09-23 15:20:13 -07:00
certificate = "/run/certs/imap/fullchain.pem";
private-key = "/run/certs/imap/key.pem";
2023-09-17 09:57:55 -07:00
};
rspamd = {
host = "antispam";
port = antispamPort;
};
2023-09-21 18:01:44 -07:00
ldap-conf = "/run/dovecot2/conf.d/ldap.conf";
2023-09-17 09:57:55 -07:00
};
}
];
};
};
ldap-proxy.service = mkIf (cfg.ldap-proxy != null) {
image = cfg.images.ldap-proxy;
restart = "always";
networks = [
"internal_network"
# Needs access to external network for user lookups
"external_network"
];
envFile = hostSecrets.mailLdapProxyEnv.target-file;
};
antispam = {
networks = [
"internal_network"
# Needs external access for blacklist checks
"external_network"
];
nixos = {
useSystemd = true;
configuration = [
(import ./rspamd.nix)
{
boot.tmpOnTmpfs = true;
system.nssModules = lib.mkForce [ ];
fudo.mail.rspamd = {
enable = true;
ports = {
milter = antispamPort;
controller = antispamControllerPort;
metrics = metricsPort;
};
antivirus = {
host = "antivirus";
port = antivirusPort;
};
};
}
];
};
};
antivirus = {
networks = [
"internal_network"
# Needs external access for database updates
"external_network"
];
2023-09-21 18:01:44 -07:00
user = mkUserMap "mailserver-antivirus";
volumes = [ "${cfg.state-directory}/antivirus:/state" ];
2023-09-17 09:57:55 -07:00
nixos = {
useSystemd = true;
configuration = [
(import ./clamav.nix)
{
boot.tmpOnTmpfs = true;
system.nssModules = lib.mkForce [ ];
fudo.mail.clamav = {
enable = true;
2023-09-21 18:01:44 -07:00
state-directory = "/state";
2023-09-17 09:57:55 -07:00
port = antispamPort;
};
}
];
};
};
dkim = {
networks = [ "internal_network" ];
2023-09-21 18:01:44 -07:00
user = mkUserMap "mailserver-dkim";
volumes = [ "${cfg.state-directory}/dkim:/state" ];
2023-09-17 09:57:55 -07:00
nixos = {
useSystemd = true;
configuration = [
(import ./dkim.nix)
{
boot.tmpOnTmpfs = true;
system.nssModules = lib.mkForce [ ];
fudo.mail.dkim = {
enable = true;
debug = cfg.debug;
domains = [ cfg.primary-domain ] ++ cfg.extra-domains;
};
port = dkimPort;
2023-09-21 18:01:44 -07:00
state-directory = "/state";
2023-09-17 09:57:55 -07:00
}
];
};
};
metrics-proxy = {
networks = [ "internal_network" ];
ports = [ "${cfg.metricsPort}:80" ];
nixos = {
useSystemd = true;
configuration = {
boot.tmpOnTmpfs = true;
system.nssModules = lib.mkForce [ ];
services.nginx = {
enable = true;
recommendedProxySettings = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
virtualHosts.localhost = {
default = true;
locations = {
"/postfix" = {
proxyPass = "http://smtp:${metricsPort}/";
};
"/dovecot" = {
proxyPass = "http://imap:${metricsPort}/";
};
"rspamd" = {
proxyPass = "http://antispam:${metricsPort}/";
};
};
};
};
};
};
};
};
};
in { imports = [ image ]; };
};
}