LDAP with password

This commit is contained in:
niten 2023-09-21 18:01:44 -07:00
parent 2ebd024977
commit 5b7eb05635
4 changed files with 244 additions and 122 deletions

View File

@ -54,7 +54,6 @@ in {
description = "Port on which to serve metrics data.";
default = 5034;
};
};
mail-user = mkOption {
@ -153,7 +152,7 @@ in {
specialUse = "Sent";
};
Archive = {
atuo = "no";
auto = "no";
specialUse = "Archive";
};
Flagged = {
@ -180,44 +179,9 @@ in {
default = 5;
};
ldap = let
ldapOpts = {
options = {
host = mkOption {
type = str;
description = "LDAP hostname.";
};
port = mkOption {
type = str;
description = "Port on which LDAP is listening.";
};
base = mkOption {
type = str;
description = "Base of the LDAP server database.";
example = "dc=mydomain,dc=org";
};
bind-dn = mkOption {
type = str;
description = ''
DN used for fetching user information.
Needs access to homeDirectory, uidNumber, gidNumber, and uid, but not
password attributes.
'';
};
bind-password-file = mkOption {
type = str;
description = "Path to file containing bind password for bind-dn.";
};
};
};
in mkOption {
type = nullOr ldapOpts;
default = null;
ldap-conf = mkOption {
type = str;
description = "Path to LDAP dovecot2 configuration.";
};
};
@ -240,7 +204,7 @@ in {
group = cfg.mail-group;
};
"${cfg.metrics.user}" = {
isSystemUser = true;
isySstemUser = true;
group = cfg.metrics.group;
};
};
@ -306,6 +270,9 @@ in {
name = "rspam_pipe_bin";
paths = [ learnHam learnSpam ];
};
mailUserUid = config.users.users."${cfg.mail-user}".uid;
mailUserGid = config.users.group."${cfg.mail-group}".gid;
in ''
## Extra Config
@ -331,6 +298,8 @@ in {
# When looking up usernames, just use the name, not the full address
auth_username_format = %n
auth_mechanisms = login plain
service lmtp {
# Enable logging in debug mode
${optionalString cfg.debug "executable = lmtp -L"}
@ -346,14 +315,13 @@ in {
# user = root
}
auth_mechanisms = login plain
${optionalString (cfg.dovecot.ldap != null) ''
passdb {
driver = ldap
args = ${cfg.dovecot.ldap.generated-ldap-config}
}
''}
passdb {
driver = ldap
args = ${cfg.ldap-conf}
}
# All users map to one actual system user
userdb {
driver = static
args = uid=${
@ -361,23 +329,6 @@ in {
} home=${cfg.state-directory}/mail/%u
}
# Used by postfix to authorize users
service auth {
inet_listener auth {
address = 0.0.0.0
port = ${toString cfg.ports.auth}
}
inet_listener auth-userdb {
address = 0.0.0.0
port = ${toString cfg.ports.userdb}
}
}
service auth-worker {
user = ${config.services.dovecot2.user}
idle_kill = 3
}
service imap {
vsz_limit = 1024M
}
@ -402,7 +353,7 @@ in {
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_before = file:${sievePath}/report-ham.sieve
sieve_pipe_bin_dir = ${pipeBin}/pipe/bin
sieve_pipe_bin_dir = ${pipeBin}/bin
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
}

View File

@ -75,6 +75,44 @@ in {
};
};
fudo.secrets.host-secrets."${hostname}" = {
dovecotLdapConfig = {
source-file = pkgs.writeText "dovecot-ldap.conf"
(concatStringsSep "\n" [
"uris = ldap://ldap-proxymine:${ldapPort}"
"ldap_version = 3"
"dn = ${cfg.ldap.bind-dn}"
"dnpass = ${readFile cfg.ldap.bind-password-file}"
"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";
};
};
users.users = {
mailserver-dovecot = {
uid = 4455;
isSystemUser = true;
};
mailserver-antivirus = {
uid = 4456;
isSystemUser = true;
};
mailserver-dkim = {
uid = 4457;
isSystemUser = true;
};
};
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 - - -"
];
virtualisation.arion.projects.mail-server.settings = let
image = { pkgs, ... }: {
project.name = "fudo-mailserver";
@ -89,6 +127,10 @@ in {
authPort = 5447;
userdbPort = 5448;
metricsPort = 5034;
mkUserMap = username:
let uid = config.users.users."${username}".uid;
in "${uid}:${uid}";
in {
smtp = {
networks = [
@ -96,14 +138,19 @@ in {
# Needs access to internet to forward emails
"external_network"
];
volumes = [
"${hostSecrets.dovecotLdapConfig.target-file}:/run/dovecot2/conf.d/ldap.conf:ro"
];
ports = [ "25:25" "587:587" "465:465" "2525:2525" ];
nixos = {
useSystemd = true;
configuration = [
(import ./postfix.nix)
(import ./dovecot.nix)
{
boot.tmpOnTmpfs = true;
system.nssModules = lib.mkForce [ ];
fudo.mail.postfix = {
enable = true;
debug = cfg.debug;
@ -127,6 +174,19 @@ in {
sasl-domain = cfg.sasl-domain;
message-size-limit = cfg.message-size-limit;
ports = { metrics = metricsPort; };
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";
};
}
];
@ -135,6 +195,11 @@ in {
imap = {
networks = [ "internal_network" ];
ports = [ "143:143" "993:993" ];
user = mkUserMap "mailserver-dovecot";
volumes = [
"${cfg.state-directory}/dovecot:/state"
"${hostSecrets.dovecotLdapConfig.target-file}:/run/dovecot2/conf.d/ldap.conf:ro"
];
nixos = {
useSystemd = true;
configuration = [
@ -145,7 +210,7 @@ in {
fudo.mail.dovecot = {
enable = true;
debug = cfg.debug;
state-directory = "${cfg.state-directory}/dovecot";
state-directory = "/state";
ports = {
lmtp = lmtpPort;
auth = authPort;
@ -162,13 +227,7 @@ in {
host = "antispam";
port = antispamPort;
};
ldap = mkIf cfg.ldap-proxy {
host = "ldap-proxy";
port = 3389;
base = cfg.ldap.base;
bind-dn = cfg.ldap.bind-dn;
bind-password-file = cfg.ldap.bind-password-file;
};
ldap-conf = "/run/dovecot2/conf.d/ldap.conf";
};
}
];
@ -219,6 +278,8 @@ in {
# Needs external access for database updates
"external_network"
];
user = mkUserMap "mailserver-antivirus";
volumes = [ "${cfg.state-directory}/antivirus:/state" ];
nixos = {
useSystemd = true;
configuration = [
@ -228,7 +289,7 @@ in {
system.nssModules = lib.mkForce [ ];
fudo.mail.clamav = {
enable = true;
state-directory = "${cfg.state-directory}/rspamd";
state-directory = "/state";
port = antispamPort;
};
}
@ -237,6 +298,8 @@ in {
};
dkim = {
networks = [ "internal_network" ];
user = mkUserMap "mailserver-dkim";
volumes = [ "${cfg.state-directory}/dkim:/state" ];
nixos = {
useSystemd = true;
configuration = [
@ -250,7 +313,7 @@ in {
domains = [ cfg.primary-domain ] ++ cfg.extra-domains;
};
port = dkimPort;
state-directory = "${cfg.state-directory}/dkim";
state-directory = "/state";
}
];
};

View File

@ -118,6 +118,44 @@ in {
default = 1725;
};
};
ldap-conf = mkOption {
type = str;
description = "Path to LDAP dovecot2 configuration.";
};
rspamd-server = {
host = mkOption {
type = str;
description = "Hostname of rspamd server.";
};
port = mkOption {
type = port;
description = "Port on which rspamd is running.";
};
};
lmtp-server = {
host = mkOption {
type = str;
description = "Hostname of lmtp server.";
};
port = mkOption {
type = port;
description = "Port on which lmtp is running.";
};
};
dkim-server = {
host = mkOption {
type = str;
description = "Hostname of dkim server.";
};
port = mkOption {
type = port;
description = "Port on which dkim is running.";
};
};
};
config = mkIf cfg.enable {
@ -140,6 +178,44 @@ in {
port = cfg.metrics-port;
};
dovecot2 = {
enable = true;
enablePAM = false;
enableImap = false;
extraConfig = ''
# Extra Config
${lib.optionalString cfg.debug "auth_debug = yes"}
# When looking up usernames, just use the name, not the full address
auth_username_format = %n
auth_mechanisms = login plain
passdb {
driver = ldap
args = ${ldapConfig}
}
userdb = {
driver = ldap
args = ${ldap-conf}
}
service auth {
unix_listener auth {
mode = 0600
user = ${config.services.postfix.user}
group = ${config.services.postfix.group}
}
}
service auth-worker {
user = ${config.services.dovecot2.user}
idle_kill = 3
}
'';
};
postfix = {
enable = true;
@ -199,6 +275,52 @@ in {
pcreFile = name: "pcre:/var/lib/postfix/conf/${name}";
mappedFile = name: "hash:/var/lib/postfix/conf/${name}";
sender-restrictions = [
"check_sender_access ${mapped-file "reject_senders"}"
"reject_sender_login_mismatch"
"reject_non_fqdn_sender"
"reject_unknown_sender_domain"
"permit_mynetworks"
"permit_sasl_authenticated"
] ++ (map (blacklist: "reject_rbl_client ${blacklist}")
cfg.blacklist.dns) ++ [ "reject" ];
relay-restrictions = [
"reject_unauth_destination"
"reject_unauth_pipelining"
"reject_unauth_destination"
"reject_unknown_sender_domain"
"permit_mynetworks"
"permit_sasl_authenticated"
] ++ (map (blacklist: "reject_rbl_client ${blacklist}")
cfg.blacklist.dns) ++ [ "reject" ];
recipient-restrictions = [
"check_sender_access ${mapped-file "reject_recipients"}"
"reject_unknown_sender_domain"
"reject_unknown_recipient_domain"
"reject_unauth_pipelining"
"reject_unauth_destination"
"reject_invalid_hostname"
"reject_non_fqdn_hostname"
"reject_non_fqdn_sender"
"reject_non_fqdn_recipient"
"check_policy_service unix:private/policy-spf"
] ++ (map (blacklist: "reject_rbl_client ${blacklist}")
cfg.blacklist.dns)
++ [ "permit_mynetworks" "permit_sasl_authenticated" "reject" ];
client-restrictions =
[ "permit_sasl_authenticated" "permit_mynetworks" "reject" ];
helo-restrictions = [
"permit_mynetworks"
"reject_invalid_hostname"
"reject_non_fqdn_helo_hostname"
"reject_unknown_helo_hostname"
] ++ (map (blacklist: "reject_rbl_client ${blacklist}")
cfg.blacklist.dns) ++ [ "permit" ];
in {
virtual_mailbox_domains = allDomains;
virtual_mailbox_maps = mappedFile "virtual_mailbox_map";
@ -209,7 +331,8 @@ in {
# virtual_gid_maps = let gid = config.users.groups."${cfg.group}".gid;
# in "static: ${toString gid}";
virtual_transport = "lmtp:unix:/run/dovecot2/dovecot-lmtp";
virtual_transport =
"lmtp:inet:${cfg.lmtp-server.host}:${cfg.lmtp-server.port}";
message_size_limit = toString (cfg.message-size-limit * 1024 * 1024);
@ -226,6 +349,7 @@ in {
smtpd_sasl_path = "/run/dovecot2/auth";
smtpd_sasl_auth_enable = "yes";
smtpd_sasl_local_domain = cfg.sasl-domain;
smtp_sasl_authenticated_header = "yes";
smtpd_sasl_security_options = "noanonymous";
smtpd_sasl_tls_security_options = "noanonymous";
@ -241,47 +365,24 @@ in {
"i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}";
smtpd_milters = [
"unix:/run/rspamd/rspamd-milter.sock"
"unix:/var/run/opendkim/opendkim.sock"
"inet:${cfg.rspamd-server.host}:${cfg.rspamd-server.port}"
"inet:${cfg.dkim.host}:${cfg.dkim.port}"
];
non_smtpd_milters = [
"unix:/run/rspamd/rspamd-milter.sock"
"unix:/var/run/opendkim/opendkim.sock"
"inet:${cfg.rspamd-server.host}:${cfg.rspamd-server.port}"
"inet:${cfg.dkim.host}:${cfg.dkim.port}"
];
smtpd_relay_restrictions = [
"permit_mynetworks"
"permit_sasl_authenticated"
"reject_unauth_destination"
"reject_unauth_pipelining"
"reject_unauth_destination"
"reject_unknown_sender_domain"
];
helo_required = true;
smtpd_sender_restrictions = [
"check_sender_access ${mapped-file "reject_senders"}"
"permit_mynetworks"
"permit_sasl_authenticated"
"reject_unknown_sender_domain"
];
smtpd_relay_restrictions = relay-restrictions;
smtpd_recipient_restrictions = [
"check_sender_access ${mapped-file "reject_recipients"}"
"permit_mynetworks"
"permit_sasl_authenticated"
"check_policy_service unix:private/policy-spf"
"reject_unknown_recipient_domain"
"reject_unauth_pipelining"
"reject_unauth_destination"
"reject_invalid_hostname"
"reject_non_fqdn_hostname"
"reject_non_fqdn_sender"
"reject_non_fqdn_recipient"
];
smtpd_sender_restrictions = sender-restrictions;
smtpd_helo_restrictions =
[ "permit_mynetworks" "reject_invalid_hostname" "permit" ];
smtpd_recipient_restrictions = recipient-restrictions;
smtpd_helo_restrictions = helo-restrictions;
# Handled by submission
smtpd_tls_security_level = "may";
@ -328,16 +429,16 @@ in {
smtpd_sasl_path = "/run/dovecot2/auth";
smtpd_sasl_security_options = "noanonymous";
smtpd_sasl_local_domain = cfg.domain;
smtpd_client_restrictions = "permit_sasl_authenticated,reject";
smtpd_sender_restrictions =
"reject_sender_login_mismatch,reject_unknown_sender_domain";
smtpd_recipient_restrictions =
"reject_non_fqdn_recipient,reject_unknown_recipient_domain,permit_sasl_authenticated,reject";
smtpd_client_restrictions = client-restrictions;
smtpd_sender_restrictions = sender-restrictions;
smtpd_recipient_restrictions = recipient-restrictions;
cleanup_service_name = "submission-header-cleanup";
};
masterConfig = {
"policy-spf" = let
# See: http://www.postfix.org/smtp.8.html
lmtp.args = [ "flags=DO" ];
policy-spf = let
policySpfFile = pkgs.writeText "policyd-spf.conf"
(cfg.postfix.policy-spf.extraConfig
+ (lib.optionalString cfg.debug "debugLevel = 4"));
@ -352,18 +453,19 @@ in {
"${policydSpf}"
];
};
"submission-header-cleanup" = let
submission-header-cleanup = let
submissionHeaderCleanupRules =
pkgs.writeText "submission_header_cleanup_rules" ''
# Removes sensitive headers from mails handed in via the submission port.
# See https://thomas-leister.de/mailserver-debian-stretch/
# Uses "pcre" style regex.
/^Received:/ IGNORE
/^X-Originating-IP:/ IGNORE
/^X-Mailer:/ IGNORE
/^User-Agent:/ IGNORE
/^X-Enigmail:/ IGNORE
/^Received:/ IGNORE
/^X-Originating-IP:/ IGNORE
/^X-Mailer:/ IGNORE
/^User-Agent:/ IGNORE
/^X-Enigmail:/ IGNORE
/^Message-ID:\s+<(.*?)@.*?>/ REPLACE Message-ID: <$1@${cfg.hostname}>
'';
in {
type = "unix";

View File

@ -62,6 +62,12 @@ in {
scan_mime_parts = false; # scan mail as a whole unit, not parts. seems to be needed to work at all
}
'';
"rbl.conf".text = ''
rbls {
an_rbl
}
'';
};
overrides."milter_headers.conf".text = "extended_spam_headers = true;";