nixos/applications/email.nix

239 lines
7.2 KiB
Nix

{ config, pkgs, lib, inputs, ...}: with lib;
let
cfg = config.services.skynet_email;
# create teh new strings
create_filter_array = map (x: "(memberOf=cn=${x},ou=groups,${cfg.ldap.base})");
create_filter_join = (x: concatStringsSep "" x);
# thought you could escape racket?
create_filter = (groups: create_filter_join (create_filter_array groups) );
in {
imports = [
./dns.nix
./acme.nix
./nginx.nix
inputs.simple-nixos-mailserver.nixosModule
];
options.services.skynet_email = {
# options that need to be passed in to make this work
enable = mkEnableOption "Skynet Email";
host = {
ip = mkOption {
type = types.str;
};
name = mkOption {
type = types.str;
};
};
domain = mkOption {
type = types.str;
default = "skynet.ie";
description = lib.mdDoc "domaino";
};
sub = mkOption {
type = types.str;
default = "mail";
description = lib.mdDoc "mailserver subdomain";
};
groups = mkOption {
type = types.listOf types.str;
default = [
# general skynet users
"skynet-users"
# C&S folsk get access
"skynet-cns"
# skynet service accounts
"skynet-service"
];
description = lib.mdDoc "Groups we want to allow access to the email";
};
ldap = {
hosts = mkOption {
type = types.listOf types.str;
default = [
"ldaps://account.skynet.ie"
];
description = lib.mdDoc "ldap domains";
};
base = mkOption {
type = types.str;
default = "dc=skynet,dc=ie";
description = lib.mdDoc "where to find users";
};
searchBase = mkOption {
type = types.str;
default = "ou=users,${cfg.ldap.base}";
description = lib.mdDoc "where to find users";
};
bind_dn = mkOption {
type = types.str;
default = "cn=admin,${cfg.ldap.base}";
description = lib.mdDoc "where to find users";
};
};
};
config = mkIf cfg.enable {
services.skynet_backup.normal.backups = [
"/var/vmail"
"/var/dkim"
];
age.secrets.ldap_pw.file = ../secrets/ldap/pw.age;
skynet_acme.domains = [
"${cfg.sub}.${cfg.domain}"
];
# set up dns record for it
skynet_dns.records = [
# basic one
{record="mail"; r_type="A"; value=cfg.host.ip;}
# TXT records, all tehse are inside escaped strings to allow using ""
# SPF record
{record="${cfg.domain}."; r_type="TXT"; value=''"v=spf1 a:${cfg.sub}.${cfg.domain} -all"'';}
# DKIM keys
{record="mail._domainkey.skynet.ie."; r_type="TXT"; value=''"v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCxju1Ie60BdHwyFVPNQKovL/cX9IFPzBKgjnHZf+WBzDCFKSBpf7NvnfXajtFDQN0poaN/Qfifid+V55ZCNDBn8Y3qZa4Y69iNiLw2DdvYf0HdnxX6+pLpbmj7tikGGLJ62xnhkJhoELnz5gCOhpyoiv0tSQVaJpaGZmoll861/QIDAQAB"'';}
{record="mail._domainkey.ulcompsoc.ie."; r_type="TXT"; value=''"v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDl8ptSASx37t5sfmU2d2Y6yi9AVrsNFBZDmJ2uaLa4NuvAjxGQCw4wx+1Jui/HOuKYLpntLsjN851wgPR+3i51g4OblqBDvcHn9NYgWRZfHj9AASANQjdsaAbkXuyKuO46hZqeWlpESAcD6a4Evam4fkm+kiZC0+rccb4cWgsuLwIDAQAB"'';}
# DMARC
{record="_dmarc.${cfg.domain}."; r_type="TXT"; value=''"v=DMARC1; p=none"'';}
# reverse pointer
{record=cfg.host.ip; r_type="PTR"; value="${cfg.sub}.${cfg.domain}.";}
# SRV records to help gmail on android etc find the correct mail.skynet.ie domain for config rather than just defaulting to skynet.ie
# https://serverfault.com/questions/935192/how-to-setup-auto-configure-email-for-android-mail-app-on-your-server/1018406#1018406
# response should be:
# _imap._tcp SRV 0 1 143 imap.example.com.
{record="_imaps._tcp"; r_type="SRV"; value="0 1 993 ${cfg.sub}.${cfg.domain}.";}
{record="_imap._tcp"; r_type="SRV"; value="0 1 143 ${cfg.sub}.${cfg.domain}.";}
{record="_submissions._tcp"; r_type="SRV"; value="0 1 465 ${cfg.sub}.${cfg.domain}.";}
{record="_submission._tcp"; r_type="SRV"; value="0 1 587 ${cfg.sub}.${cfg.domain}.";}
];
# to provide the certs
services.nginx.virtualHosts = {
"${cfg.sub}.${cfg.domain}" = {
forceSSL = true;
useACMEHost = "skynet";
# override the inbuilt nginx config
enableACME = false;
serverName = "${cfg.sub}.${cfg.domain}";
};
};
#https://nixos-mailserver.readthedocs.io/en/latest/add-roundcube.html
users.groups.nginx = {};
users.groups.roundcube = {};
services.roundcube = {
enable = true;
# this is the url of the vhost, not necessarily the same as the fqdn of
# the mailserver
hostName = "${cfg.sub}.${cfg.domain}";
extraConfig = ''
# starttls needed for authentication, so the fqdn required to match
# the certificate
$config['smtp_server'] = "ssl://${cfg.sub}.${cfg.domain}";
$config['smtp_user'] = "%u";
$config['smtp_pass'] = "%p";
$config['imap_host'] = "ssl://${cfg.sub}.${cfg.domain}";
$config['product_name'] = "Skynet Webmail";
$config['identities_level'] = 4;
$config['login_username_filter'] = "email";
$config['ldap_public']['public'] = array(
'name' => 'Public LDAP Addressbook',
'hosts' => 'tls://account.skynet.ie',
'port' => 636 ,
'user_specific' => false,
'base_dn' => 'ou=users,dc=skynet,dc=ie',
'filter' => '(skMemberOf=cn=skynet-users-linux,ou=groups,dc=skynet,dc=ie)',
'fieldmap' => [
// Roundcube => LDAP:limit
'name' => 'cn',
'surname' => 'sn',
'email' => 'skMail:*',
]
);
'';
};
mailserver = {
enable = true;
fqdn = "${cfg.sub}.${cfg.domain}";
domains = [
cfg.domain
];
# use the letsencrypt certs
certificateScheme = "acme";
# 20MB max size
messageSizeLimit = 20000000;
ldap = {
enable = true;
uris = cfg.ldap.hosts;
bind = {
dn = cfg.ldap.bind_dn;
passwordFile = config.age.secrets.ldap_pw.path;
};
searchBase = cfg.ldap.searchBase;
searchScope = "sub";
dovecot = {
userFilter = "(skMail=%u)";
# can lock down how much space each user has access to from ldap
userAttrs = "quotaEmail=quota_rule=*:bytes=%$,=quota_rule2=Trash:storage=+100M";
# accept emails in, but only allow access to paid up members
passFilter = "(&(|${create_filter cfg.groups})(skMail=%u))";
};
postfix = {
filter = "(|(skMail=%s)(uid=%s))";
uidAttribute = "skMail";
mailAttribute = "skMail";
};
};
# feckin spammers
rejectRecipients = [
];
};
# tune the spam filter
/*
services.rspamd.extraConfig = ''
actions {
reject = null; # Disable rejects, default is 15
add_header = 7; # Add header when reaching this score
greylist = 4; # Apply greylisting when reaching this score
}
'';
*/
};
}