changes
This commit is contained in:
1
hosts/mail.social-grow.tech/channel
Normal file
1
hosts/mail.social-grow.tech/channel
Normal file
@@ -0,0 +1 @@
|
||||
https://channels.nixos.org/nixos-24.05
|
||||
49
hosts/mail.social-grow.tech/configuration.nix
Normal file
49
hosts/mail.social-grow.tech/configuration.nix
Normal file
@@ -0,0 +1,49 @@
|
||||
{ config, pkgs, ... }:
|
||||
{
|
||||
imports = [
|
||||
./utils/bento.nix
|
||||
./utils/modules/sops.nix
|
||||
./utils/modules/lego/lego.nix
|
||||
|
||||
# ./modules/self-service-password.nix
|
||||
./modules/rspamd.nix
|
||||
./modules/openldap.nix
|
||||
./modules/dovecot.nix
|
||||
./modules/postfix.nix
|
||||
|
||||
./utils/modules/borgbackup.nix
|
||||
./utils/modules/promtail
|
||||
./utils/modules/victoriametrics
|
||||
./utils/modules/netdata.nix
|
||||
|
||||
./hardware-configuration.nix
|
||||
];
|
||||
|
||||
|
||||
sops.defaultSopsFile = ./secrets.yaml;
|
||||
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||
|
||||
networking.hostName = "mail";
|
||||
networking.domain = "cloonar.com";
|
||||
|
||||
services.openssh.enable = true;
|
||||
users.users.root.openssh.authorizedKeys.keys = [
|
||||
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDN/2SAFm50kraB1fepAizox/QRXxB7WbqVbH+5OPalDT47VIJGNKOKhixQoqhABHxEoLxdf/C83wxlCVlPV9poLfDgVkA3Lyt5r3tSFQ6QjjOJAgchWamMsxxyGBedhKvhiEzcr/Lxytnoz3kjDG8fqQJwEpdqMmJoMUfyL2Rqp16u+FQ7d5aJtwO8EUqovhMaNO7rggjPpV/uMOg+tBxxmscliN7DLuP4EMTA/FwXVzcFNbOx3K9BdpMRAaSJt4SWcJO2cS2KHA5n/H+PQI7nz5KN3Yr/upJN5fROhi/SHvK39QOx12Pv7FCuWlc+oR68vLaoCKYhnkl3DnCfc7A7"
|
||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIRQuPqH5fdX3KEw7DXzWEdO3AlUn1oSmtJtHB71ICoH Generated By Termius"
|
||||
];
|
||||
|
||||
# backups
|
||||
borgbackup.repo = "u149513-sub7@u149513-sub7.your-backup.de:borg";
|
||||
|
||||
networking.firewall = {
|
||||
enable = true;
|
||||
allowedTCPPorts = [ 22 80 443 ];
|
||||
};
|
||||
|
||||
nix.gc = {
|
||||
automatic = true;
|
||||
options = "--delete-older-than 60d";
|
||||
};
|
||||
|
||||
system.stateVersion = "22.11";
|
||||
}
|
||||
15
hosts/mail.social-grow.tech/hardware-configuration.nix
Normal file
15
hosts/mail.social-grow.tech/hardware-configuration.nix
Normal file
@@ -0,0 +1,15 @@
|
||||
{ modulesPath, ... }:
|
||||
{
|
||||
imports = [ (modulesPath + "/profiles/qemu-guest.nix") ];
|
||||
boot.loader.grub = {
|
||||
efiSupport = true;
|
||||
efiInstallAsRemovable = true;
|
||||
device = "nodev";
|
||||
configurationLimit = 2;
|
||||
};
|
||||
fileSystems."/boot" = { device = "/dev/disk/by-uuid/105A-0CC0"; fsType = "vfat"; };
|
||||
boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd" "xen_blkfront" ];
|
||||
boot.initrd.kernelModules = [ "nvme" ];
|
||||
fileSystems."/" = { device = "/dev/sda1"; fsType = "ext4"; };
|
||||
|
||||
}
|
||||
266
hosts/mail.social-grow.tech/modules/dovecot.nix
Normal file
266
hosts/mail.social-grow.tech/modules/dovecot.nix
Normal file
@@ -0,0 +1,266 @@
|
||||
{ pkgs
|
||||
, config
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
domain = config.networking.domain;
|
||||
# domain = "cloonar.com";
|
||||
|
||||
ldapConfig = pkgs.writeText "dovecot-ldap.conf" ''
|
||||
hosts = ldap.cloonar.com
|
||||
tls = yes
|
||||
dn = "cn=vmail,ou=system,ou=users,dc=cloonar,dc=com"
|
||||
dnpass = "@ldap-password@"
|
||||
auth_bind = no
|
||||
ldap_version = 3
|
||||
base = ou=users,dc=%Dd
|
||||
user_filter = (&(objectClass=mailAccount)(mail=%u))
|
||||
user_attrs = \
|
||||
quota=quota_rule=*:bytes=%$, \
|
||||
=home=/var/vmail/%d/%n/, \
|
||||
=mail=maildir:/var/vmail/%d/%n/Maildir
|
||||
pass_attrs = mail=user,userPassword=password
|
||||
pass_filter = (&(objectClass=mailAccount)(mail=%u))
|
||||
iterate_attrs = =user=%{ldap:mail}
|
||||
iterate_filter = (objectClass=mailAccount)
|
||||
scope = subtree
|
||||
default_pass_scheme = CRYPT
|
||||
'';
|
||||
|
||||
doveSync = pkgs.writeShellScriptBin "dove-sync.sh" ''
|
||||
#!/usr/bin/env bash
|
||||
SERVER=''${1}
|
||||
|
||||
if [ -z "$SERVER" ]; then
|
||||
echo "use as dove-sync.sh host.example.com"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
doveadm user *@cloonar.com | while read user; do
|
||||
doveadm -v sync -u $user $SERVER
|
||||
done
|
||||
|
||||
doveadm user *@optiprot.eu | while read user; do
|
||||
doveadm -v sync -u $user $SERVER
|
||||
done
|
||||
|
||||
doveadm user *@superbros.tv | while read user; do
|
||||
doveadm -v sync -u $user $SERVER
|
||||
done
|
||||
|
||||
doveadm user *@ghetto.at | while read user; do
|
||||
doveadm -v sync -u $user $SERVER
|
||||
done
|
||||
|
||||
doveadm user *@szaku-consulting.at | while read user; do
|
||||
doveadm -v sync -u $user $SERVER
|
||||
done
|
||||
|
||||
doveadm user *@korean-skin.care | while read user; do
|
||||
doveadm -v sync -u $user $SERVER
|
||||
done
|
||||
'';
|
||||
|
||||
quotaWarning = pkgs.writeShellScriptBin "quota-warning.sh" ''
|
||||
#!/usr/bin/env bash
|
||||
PERCENT=''${1}
|
||||
USER=''${2}
|
||||
|
||||
cat << EOF | /usr/lib/dovecot/deliver -d ''${USER} -o "plugin/quota=dict:User quota::noenforcing:proxy::quotadict"
|
||||
From: no-reply@$(hostname -f)
|
||||
Subject: Warning: Your mailbox is now ''${PERCENT}% full.
|
||||
|
||||
Your mailbox is now ''${PERCENT}% full, please clean up some mails for further incoming mails.
|
||||
EOF
|
||||
|
||||
if [ ''${PERCENT} -ge 95 ]; then
|
||||
DOMAIN="$(echo ''${USER} | awk -F'@' '{print $2}')"
|
||||
cat << EOF | /usr/lib/dovecot/deliver -d postmaster@''${DOMAIN} -o "plugin/quota=dict:User quota::noenforcing:proxy::quotadict"
|
||||
From: no-reply@$(hostname -f)
|
||||
Subject: Mailbox Quota Warning: ''${PERCENT}% full, ''${USER}
|
||||
|
||||
Mailbox (''${USER}) is now ''${PERCENT}% full, please clean up some mails for
|
||||
further incoming mails.
|
||||
EOF
|
||||
fi
|
||||
'';
|
||||
in
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
doveSync
|
||||
];
|
||||
|
||||
services.dovecot2 = {
|
||||
enable = true;
|
||||
enableImap = true;
|
||||
enableLmtp = true;
|
||||
enablePAM = false;
|
||||
mailLocation = "maildir:/var/vmail/%d/%n/Maildir";
|
||||
mailUser = "vmail";
|
||||
mailGroup = "vmail";
|
||||
extraConfig = ''
|
||||
ssl = yes
|
||||
ssl_cert = </var/lib/acme/imap.${domain}/fullchain.pem
|
||||
ssl_key = </var/lib/acme/imap.${domain}/key.pem
|
||||
ssl_min_protocol = TLSv1.2
|
||||
ssl_cipher_list = EECDH+AESGCM:EDH+AESGCM
|
||||
ssl_prefer_server_ciphers = yes
|
||||
ssl_dh=<${config.security.dhparams.params.dovecot2.path}
|
||||
|
||||
mail_plugins = virtual fts fts_lucene quota acl
|
||||
|
||||
service lmtp {
|
||||
user = vmail
|
||||
unix_listener /var/lib/postfix/queue/private/dovecot-lmtp {
|
||||
group = postfix
|
||||
mode = 0600
|
||||
user = postfix
|
||||
}
|
||||
}
|
||||
|
||||
service doveadm {
|
||||
inet_listener {
|
||||
port = 4170
|
||||
ssl = yes
|
||||
}
|
||||
}
|
||||
protocol imap {
|
||||
mail_plugins = $mail_plugins imap_quota imap_acl
|
||||
}
|
||||
protocol lmtp {
|
||||
postmaster_address=postmaster@${domain}
|
||||
hostname=mail.cloonar.com
|
||||
mail_plugins = $mail_plugins sieve
|
||||
}
|
||||
service auth {
|
||||
unix_listener auth-userdb {
|
||||
mode = 0640
|
||||
user = vmail
|
||||
group = vmail
|
||||
}
|
||||
# Postfix smtp-auth
|
||||
unix_listener /var/lib/postfix/queue/private/auth {
|
||||
mode = 0666
|
||||
user = postfix
|
||||
group = postfix
|
||||
}
|
||||
}
|
||||
userdb {
|
||||
args = /run/dovecot2/ldap.conf
|
||||
driver = ldap
|
||||
}
|
||||
passdb {
|
||||
args = /run/dovecot2/ldap.conf
|
||||
driver = ldap
|
||||
}
|
||||
|
||||
service imap-login {
|
||||
client_limit = 1000
|
||||
service_count = 0
|
||||
inet_listener imaps {
|
||||
port = 993
|
||||
}
|
||||
}
|
||||
|
||||
service managesieve-login {
|
||||
inet_listener sieve {
|
||||
port = 4190
|
||||
}
|
||||
}
|
||||
service quota-warning {
|
||||
executable = script ${quotaWarning}/bin/quota-warning.sh
|
||||
unix_listener quota-warning {
|
||||
user = vmail
|
||||
group = vmail
|
||||
mode = 0660
|
||||
}
|
||||
}
|
||||
service quota-status {
|
||||
# '-p <protocol>'. Currently only 'postfix' protocol is supported.
|
||||
executable = quota-status -p postfix
|
||||
client_limit = 1
|
||||
inet_listener {
|
||||
address = 127.0.0.1
|
||||
port = 12340
|
||||
}
|
||||
}
|
||||
|
||||
protocol sieve {
|
||||
managesieve_logout_format = bytes ( in=%i : out=%o )
|
||||
}
|
||||
|
||||
plugin {
|
||||
sieve_dir = /var/vmail/%d/%n/sieve/scripts/
|
||||
sieve = /var/vmail/%d/%n/sieve/active-script.sieve
|
||||
sieve_extensions = +vacation-seconds +editheader
|
||||
sieve_vacation_min_period = 1min
|
||||
|
||||
fts = lucene
|
||||
fts_lucene = whitespace_chars=@.
|
||||
|
||||
quota_warning = storage=100%% quota-warning 100 %u
|
||||
quota_warning2 = storage=95%% quota-warning 95 %u
|
||||
quota_warning3 = storage=90%% quota-warning 90 %u
|
||||
quota_warning4 = storage=85%% quota-warning 85 %u
|
||||
|
||||
quota_grace = 10%%
|
||||
|
||||
quota_status_success = DUNNO
|
||||
quota_status_nouser = DUNNO
|
||||
quota_status_overquota = "552 5.2.2 Mailbox is full"
|
||||
}
|
||||
|
||||
# If you have Dovecot v2.2.8+ you may get a significant performance improvement with fetch-headers:
|
||||
imapc_features = $imapc_features fetch-headers
|
||||
# Read multiple mails in parallel, improves performance
|
||||
mail_prefetch_count = 20
|
||||
'';
|
||||
modules = [
|
||||
pkgs.dovecot_pigeonhole
|
||||
];
|
||||
protocols = [
|
||||
"sieve"
|
||||
];
|
||||
};
|
||||
|
||||
users.users.vmail = {
|
||||
home = "/var/vmail";
|
||||
createHome = true;
|
||||
isSystemUser = true;
|
||||
uid = 1000;
|
||||
shell = "/run/current-system/sw/bin/nologin";
|
||||
};
|
||||
|
||||
security.dhparams = {
|
||||
enable = true;
|
||||
params.dovecot2 = { };
|
||||
};
|
||||
|
||||
sops.secrets.dovecot-ldap-password = { };
|
||||
|
||||
systemd.services.dovecot2.preStart = ''
|
||||
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${ldapConfig} > /run/dovecot2/ldap.conf
|
||||
'';
|
||||
|
||||
systemd.services.dovecot2 = {
|
||||
wants = [ "acme-imap.${domain}.service" ];
|
||||
after = [ "acme-imap.${domain}.service" ];
|
||||
};
|
||||
|
||||
users.groups.acme.members = [ "openldap" ];
|
||||
|
||||
/* trigger the actual certificate generation for your hostname */
|
||||
security.acme.certs."imap.${domain}" = {
|
||||
extraDomainNames = [
|
||||
"imap-test.${domain}"
|
||||
"imap-02.${domain}"
|
||||
];
|
||||
postRun = "systemctl restart dovecot2.service";
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
143 # imap
|
||||
993 # imaps
|
||||
4190 # sieve
|
||||
];
|
||||
}
|
||||
508
hosts/mail.social-grow.tech/modules/openldap.nix
Normal file
508
hosts/mail.social-grow.tech/modules/openldap.nix
Normal file
@@ -0,0 +1,508 @@
|
||||
{
|
||||
pkgs,
|
||||
config,
|
||||
...
|
||||
}:
|
||||
let
|
||||
domain = config.networking.domain;
|
||||
# domain = "cloonar.com";
|
||||
in {
|
||||
services.openldap = {
|
||||
enable = true;
|
||||
|
||||
urlList = [ "ldap:///" "ldaps:///" ];
|
||||
|
||||
settings.attrs = {
|
||||
olcLogLevel = "-1";
|
||||
|
||||
olcTLSCACertificateFile = "/var/lib/acme/ldap.${domain}/full.pem";
|
||||
olcTLSCertificateFile = "/var/lib/acme/ldap.${domain}/cert.pem";
|
||||
olcTLSCertificateKeyFile = "/var/lib/acme/ldap.${domain}/key.pem";
|
||||
olcTLSCipherSuite = "HIGH:MEDIUM:+3DES:+RC4:+aNULL";
|
||||
olcTLSCRLCheck = "none";
|
||||
olcTLSVerifyClient = "never";
|
||||
olcTLSProtocolMin = "3.1";
|
||||
olcSecurity = "tls=1";
|
||||
};
|
||||
|
||||
settings.children = {
|
||||
"cn=schema".includes = [
|
||||
"${pkgs.openldap}/etc/schema/core.ldif"
|
||||
"${pkgs.openldap}/etc/schema/cosine.ldif"
|
||||
"${pkgs.openldap}/etc/schema/inetorgperson.ldif"
|
||||
"${pkgs.openldap}/etc/schema/nis.ldif"
|
||||
];
|
||||
|
||||
"olcDatabase={1}mdb".attrs = {
|
||||
objectClass = ["olcDatabaseConfig" "olcMdbConfig"];
|
||||
|
||||
olcDatabase = "{1}mdb";
|
||||
olcDbDirectory = "/var/lib/openldap/data";
|
||||
|
||||
olcSuffix = "dc=cloonar,dc=com";
|
||||
|
||||
olcRootDN = "cn=admin,dc=cloonar,dc=com";
|
||||
olcRootPW.path = config.sops.secrets.openldap-rootpw.path;
|
||||
|
||||
|
||||
olcAccess = [
|
||||
''
|
||||
{0}to attrs=userPassword
|
||||
by self write
|
||||
by anonymous auth
|
||||
by dn="cn=owncloud,ou=system,ou=users,dc=cloonar,dc=com" write
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * none
|
||||
''
|
||||
''
|
||||
{1}to attrs=loginShell
|
||||
by self write
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * none
|
||||
''
|
||||
''
|
||||
{2}to dn.subtree="ou=system,ou=users,dc=cloonar,dc=com"
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * none
|
||||
''
|
||||
''
|
||||
{3}to *
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by dn="cn=admin,dc=cloonar,dc=com" write
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * none
|
||||
''
|
||||
];
|
||||
};
|
||||
"olcOverlay=memberof,olcDatabase={1}mdb".attrs = {
|
||||
objectClass = [ "olcOverlayConfig" "olcMemberOf" ];
|
||||
olcOverlay = "memberof";
|
||||
olcMemberOfRefint = "TRUE";
|
||||
};
|
||||
"olcOverlay=ppolicy,olcDatabase={1}mdb".attrs = {
|
||||
objectClass = [ "olcOverlayConfig" "olcPPolicyConfig" ];
|
||||
olcOverlay = "ppolicy";
|
||||
olcPPolicyHashCleartext = "TRUE";
|
||||
};
|
||||
# "olcOverlay=syncprov,olcDatabase={1}mdb".attrs = {
|
||||
# objectClass = ["olcOverlayConfig" "olcSyncProvConfig"];
|
||||
# olcOverlay = "syncprov";
|
||||
# olcSpSessionLog = "100";
|
||||
# };
|
||||
"olcDatabase={2}monitor".attrs = {
|
||||
olcDatabase = "{2}monitor";
|
||||
objectClass = ["olcDatabaseConfig" "olcMonitorConfig"];
|
||||
olcAccess = [
|
||||
''
|
||||
{0}to *
|
||||
by dn.exact="cn=netdata,ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by * none
|
||||
''
|
||||
];
|
||||
};
|
||||
|
||||
"olcDatabase={3}mdb".attrs = {
|
||||
objectClass = ["olcDatabaseConfig" "olcMdbConfig"];
|
||||
|
||||
olcDatabase = "{3}mdb";
|
||||
olcDbDirectory = "/var/lib/openldap/data";
|
||||
|
||||
olcSuffix = "dc=ghetto,dc=at";
|
||||
|
||||
olcAccess = [
|
||||
''
|
||||
{0}to attrs=userPassword
|
||||
by self write
|
||||
by anonymous auth
|
||||
by dn="cn=owncloud,ou=system,ou=users,dc=cloonar,dc=com" write
|
||||
by dn="cn=authelia,ou=system,ou=users,dc=cloonar,dc=com" write
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * none
|
||||
''
|
||||
''
|
||||
{1}to *
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * read
|
||||
''
|
||||
];
|
||||
};
|
||||
"olcOverlay=memberof,olcDatabase={3}mdb".attrs = {
|
||||
objectClass = [ "olcOverlayConfig" "olcMemberOf" ];
|
||||
olcOverlay = "memberof";
|
||||
olcMemberOfRefint = "TRUE";
|
||||
};
|
||||
"olcOverlay=ppolicy,olcDatabase={3}mdb".attrs = {
|
||||
objectClass = [ "olcOverlayConfig" "olcPPolicyConfig" ];
|
||||
olcOverlay = "ppolicy";
|
||||
olcPPolicyHashCleartext = "TRUE";
|
||||
};
|
||||
|
||||
"olcDatabase={4}mdb".attrs = {
|
||||
objectClass = ["olcDatabaseConfig" "olcMdbConfig"];
|
||||
|
||||
olcDatabase = "{4}mdb";
|
||||
olcDbDirectory = "/var/lib/openldap/data";
|
||||
|
||||
olcSuffix = "dc=superbros,dc=tv";
|
||||
|
||||
olcAccess = [
|
||||
''
|
||||
{0}to attrs=userPassword
|
||||
by self write
|
||||
by anonymous auth
|
||||
by dn="cn=owncloud,ou=system,ou=users,dc=cloonar,dc=com" write
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * none
|
||||
''
|
||||
''
|
||||
{1}to *
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * read
|
||||
''
|
||||
];
|
||||
};
|
||||
"olcOverlay=memberof,olcDatabase={4}mdb".attrs = {
|
||||
objectClass = [ "olcOverlayConfig" "olcMemberOf" ];
|
||||
olcOverlay = "memberof";
|
||||
olcMemberOfRefint = "TRUE";
|
||||
};
|
||||
"olcOverlay=ppolicy,olcDatabase={4}mdb".attrs = {
|
||||
objectClass = [ "olcOverlayConfig" "olcPPolicyConfig" ];
|
||||
olcOverlay = "ppolicy";
|
||||
olcPPolicyHashCleartext = "TRUE";
|
||||
};
|
||||
|
||||
|
||||
"olcDatabase={6}mdb".attrs = {
|
||||
objectClass = ["olcDatabaseConfig" "olcMdbConfig"];
|
||||
|
||||
olcDatabase = "{6}mdb";
|
||||
olcDbDirectory = "/var/lib/openldap/data";
|
||||
|
||||
olcSuffix = "dc=szaku-consulting,dc=at";
|
||||
|
||||
olcAccess = [
|
||||
''
|
||||
{0}to attrs=userPassword
|
||||
by self write
|
||||
by anonymous auth
|
||||
by dn="cn=owncloud,ou=system,ou=users,dc=cloonar,dc=com" write
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * none
|
||||
''
|
||||
''
|
||||
{1}to *
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * read
|
||||
''
|
||||
];
|
||||
};
|
||||
# "olcOverlay=memberof,olcDatabase={6}mdb".attrs = {
|
||||
# objectClass = [ "olcOverlayConfig" "olcMemberOf" ];
|
||||
# olcOverlay = "memberof";
|
||||
# olcMemberOfRefint = "TRUE";
|
||||
# };
|
||||
# "olcOverlay=ppolicy,olcDatabase={6}mdb".attrs = {
|
||||
# objectClass = [ "olcOverlayConfig" "olcPPolicyConfig" ];
|
||||
# olcOverlay = "ppolicy";
|
||||
# olcPPolicyHashCleartext = "TRUE";
|
||||
# };
|
||||
|
||||
"olcDatabase={7}mdb".attrs = {
|
||||
objectClass = ["olcDatabaseConfig" "olcMdbConfig"];
|
||||
|
||||
olcDatabase = "{7}mdb";
|
||||
olcDbDirectory = "/var/lib/openldap/data";
|
||||
|
||||
olcSuffix = "dc=myhidden,dc=life";
|
||||
|
||||
olcAccess = [
|
||||
''
|
||||
{0}to attrs=userPassword
|
||||
by self write
|
||||
by anonymous auth
|
||||
by dn="cn=owncloud,ou=system,ou=users,dc=cloonar,dc=com" write
|
||||
by dn="cn=authelia,ou=system,ou=users,dc=cloonar,dc=com" write
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * none
|
||||
''
|
||||
''
|
||||
{1}to *
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * read
|
||||
''
|
||||
];
|
||||
};
|
||||
# "olcOverlay=memberof,olcDatabase={7}mdb".attrs = {
|
||||
# objectClass = [ "olcOverlayConfig" "olcMemberOf" ];
|
||||
# olcOverlay = "memberof";
|
||||
# olcMemberOfRefint = "TRUE";
|
||||
# };
|
||||
# "olcOverlay=ppolicy,olcDatabase={7}mdb".attrs = {
|
||||
# objectClass = [ "olcOverlayConfig" "olcPPolicyConfig" ];
|
||||
# olcOverlay = "ppolicy";
|
||||
# olcPPolicyHashCleartext = "TRUE";
|
||||
# };
|
||||
|
||||
"olcDatabase={8}mdb".attrs = {
|
||||
objectClass = ["olcDatabaseConfig" "olcMdbConfig"];
|
||||
|
||||
olcDatabase = "{8}mdb";
|
||||
olcDbDirectory = "/var/lib/openldap/data";
|
||||
|
||||
olcSuffix = "dc=korean-skin,dc=care";
|
||||
|
||||
olcAccess = [
|
||||
''
|
||||
{0}to attrs=userPassword
|
||||
by self write
|
||||
by anonymous auth
|
||||
by dn="cn=owncloud,ou=system,ou=users,dc=cloonar,dc=com" write
|
||||
by dn="cn=authelia,ou=system,ou=users,dc=cloonar,dc=com" write
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * none
|
||||
''
|
||||
''
|
||||
{1}to *
|
||||
by dn.subtree="ou=system,ou=users,dc=cloonar,dc=com" read
|
||||
by group.exact="cn=Administrators,ou=groups,dc=cloonar,dc=com" write
|
||||
by * read
|
||||
''
|
||||
];
|
||||
};
|
||||
|
||||
# "cn=module{0},cn=config" = {
|
||||
# attrs = {
|
||||
# objectClass = "olcModuleList";
|
||||
# cn = "module{0}";
|
||||
# olcModuleLoad = "ppolicy.la";
|
||||
# };
|
||||
# };
|
||||
|
||||
"cn={3}cloonar,cn=schema" = {
|
||||
attrs = {
|
||||
cn = "{1}cloonar";
|
||||
objectClass = "olcSchemaConfig";
|
||||
olcObjectClasses = [
|
||||
''
|
||||
(1.3.6.1.4.1.28298.1.2.4 NAME 'cloonarUser'
|
||||
SUP (mailAccount) AUXILIARY
|
||||
DESC 'Cloonar Account'
|
||||
MAY (sshPublicKey $ ownCloudQuota $ quota))
|
||||
''
|
||||
];
|
||||
};
|
||||
};
|
||||
"cn={2}postfix,cn=schema".attrs = {
|
||||
cn = "{2}postfix";
|
||||
objectClass = "olcSchemaConfig";
|
||||
olcAttributeTypes = [
|
||||
''
|
||||
(1.3.6.1.4.1.12461.1.1.1 NAME 'postfixTransport'
|
||||
DESC 'A string directing postfix which transport to use'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{20} SINGLE-VALUE)''
|
||||
''
|
||||
(1.3.6.1.4.1.12461.1.1.5 NAME 'mailbox'
|
||||
DESC 'The absolute path to the mailbox for a mail account in a non-default location'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE)
|
||||
''
|
||||
''
|
||||
(1.3.6.1.4.1.12461.1.1.6 NAME 'quota'
|
||||
DESC 'A string that represents the quota on a mailbox'
|
||||
EQUALITY caseExactIA5Match
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 SINGLE-VALUE)
|
||||
''
|
||||
''
|
||||
(1.3.6.1.4.1.12461.1.1.8 NAME 'maildrop'
|
||||
DESC 'RFC822 Mailbox - mail alias'
|
||||
EQUALITY caseIgnoreIA5Match
|
||||
SUBSTR caseIgnoreIA5SubstringsMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{256})
|
||||
''
|
||||
];
|
||||
olcObjectClasses = [
|
||||
''
|
||||
(1.3.6.1.4.1.12461.1.2.1 NAME 'mailAccount'
|
||||
SUP top AUXILIARY
|
||||
DESC 'Mail account objects'
|
||||
MUST ( mail $ userPassword )
|
||||
MAY ( cn $ description $ quota))
|
||||
''
|
||||
''
|
||||
(1.3.6.1.4.1.12461.1.2.2 NAME 'mailAlias'
|
||||
SUP top STRUCTURAL
|
||||
DESC 'Mail aliasing/forwarding entry'
|
||||
MUST ( mail $ maildrop )
|
||||
MAY ( cn $ description ))
|
||||
''
|
||||
''
|
||||
(1.3.6.1.4.1.12461.1.2.3 NAME 'mailDomain'
|
||||
SUP domain STRUCTURAL
|
||||
DESC 'Virtual Domain entry to be used with postfix transport maps'
|
||||
MUST ( dc )
|
||||
MAY ( postfixTransport $ description ))
|
||||
''
|
||||
''
|
||||
(1.3.6.1.4.1.12461.1.2.4 NAME 'mailPostmaster'
|
||||
SUP top AUXILIARY
|
||||
DESC 'Added to a mailAlias to create a postmaster entry'
|
||||
MUST roleOccupant)
|
||||
''
|
||||
];
|
||||
};
|
||||
"cn={1}openssh,cn=schema".attrs = {
|
||||
cn = "{1}openssh";
|
||||
objectClass = "olcSchemaConfig";
|
||||
olcAttributeTypes = [
|
||||
''
|
||||
(1.3.6.1.4.1.24552.500.1.1.1.13
|
||||
NAME 'sshPublicKey'
|
||||
DESC 'MANDATORY: OpenSSH Public key'
|
||||
EQUALITY octetStringMatch
|
||||
SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 )
|
||||
''
|
||||
];
|
||||
olcObjectClasses = [
|
||||
''
|
||||
(1.3.6.1.4.1.24552.500.1.1.2.0
|
||||
NAME 'ldapPublicKey'
|
||||
SUP top AUXILIARY
|
||||
DESC 'MANDATORY: OpenSSH LPK objectclass'
|
||||
MUST ( sshPublicKey $ uid ))
|
||||
''
|
||||
];
|
||||
};
|
||||
"cn={1}nextcloud,cn=schema".attrs = {
|
||||
cn = "{1}nextcloud";
|
||||
objectClass = "olcSchemaConfig";
|
||||
olcAttributeTypes = [
|
||||
''
|
||||
(1.3.6.1.4.1.39430.1.1.1
|
||||
NAME 'ownCloudQuota'
|
||||
DESC 'User Quota (e.g. 15 GB)'
|
||||
SYNTAX '1.3.6.1.4.1.1466.115.121.1.15')
|
||||
''
|
||||
];
|
||||
olcObjectClasses = [
|
||||
''
|
||||
(1.3.6.1.4.1.39430.1.2.1
|
||||
NAME 'ownCloud'
|
||||
DESC 'ownCloud LDAP Schema'
|
||||
AUXILIARY
|
||||
MUST ( mail $ userPassword )
|
||||
MAY ( ownCloudQuota ))
|
||||
''
|
||||
];
|
||||
};
|
||||
"cn={1}gogs,cn=schema".attrs = {
|
||||
cn = "{1}gogs";
|
||||
objectClass = "olcSchemaConfig";
|
||||
olcObjectClasses = [
|
||||
''
|
||||
( 1.3.6.1.4.1.28293.1.2.4 NAME 'gitlab'
|
||||
SUP uidObject AUXILIARY
|
||||
DESC 'Added to an account to allow gitlab access'
|
||||
MUST (mail))
|
||||
''
|
||||
];
|
||||
};
|
||||
"cn={1}homeAssistant,cn=schema".attrs = {
|
||||
cn = "{1}homeAssistant";
|
||||
objectClass = "olcSchemaConfig";
|
||||
olcObjectClasses = [
|
||||
''
|
||||
(1.3.6.1.4.1.28297.1.2.4 NAME 'homeAssistant'
|
||||
SUP uidObject AUXILIARY
|
||||
DESC 'Added to an account to allow home-assistant access'
|
||||
MUST (mail) )
|
||||
''
|
||||
];
|
||||
};
|
||||
# "cn={1}ttrss,cn=schema".attrs = {
|
||||
# cn = "{1}ttrss";
|
||||
# objectClass = "olcSchemaConfig";
|
||||
# olcObjectClasses = [
|
||||
# ''
|
||||
# ( 1.3.6.1.4.1.28294.1.2.4 NAME 'ttrss'
|
||||
# SUP top AUXILIARY
|
||||
# DESC 'Added to an account to allow tinytinyrss access'
|
||||
# MUST ( mail $ userPassword ))
|
||||
# ''
|
||||
# ];
|
||||
# };
|
||||
# "cn={1}prometheus,cn=schema".attrs = {
|
||||
# cn = "{1}prometheus";
|
||||
# objectClass = "olcSchemaConfig";
|
||||
# olcObjectClasses = [
|
||||
# ''
|
||||
# ( 1.3.6.1.4.1.28296.1.2.4
|
||||
# NAME 'prometheus'
|
||||
# SUP uidObject AUXILIARY
|
||||
# DESC 'Added to an account to allow prometheus access'
|
||||
# MUST (mail))
|
||||
# ''
|
||||
# ];
|
||||
# };
|
||||
# "cn={1}loki,cn=schema".attrs = {
|
||||
# cn = "{1}loki";
|
||||
# objectClass = "olcSchemaConfig";
|
||||
# olcObjectClasses = [
|
||||
# ''
|
||||
# ( 1.3.6.1.4.1.28299.1.2.4
|
||||
# NAME 'loki'
|
||||
# SUP uidObject AUXILIARY
|
||||
# DESC 'Added to an account to allow loki access'
|
||||
# MUST (mail))
|
||||
# ''
|
||||
# ];
|
||||
# };
|
||||
# "cn={1}flood,cn=schema".attrs = {
|
||||
# cn = "{1}flood";
|
||||
# objectClass = "olcSchemaConfig";
|
||||
# olcObjectClasses = [
|
||||
# ''
|
||||
# (1.3.6.1.4.1.28300.1.2.4 NAME 'flood'
|
||||
# SUP uidObject AUXILIARY
|
||||
# DESC 'Added to an account to allow flood access'
|
||||
# MUST (mail))
|
||||
# ''
|
||||
# ];
|
||||
# };
|
||||
};
|
||||
};
|
||||
|
||||
/* ensure openldap is launched after certificates are created */
|
||||
systemd.services.openldap = {
|
||||
wants = [ "acme-${domain}.service" ];
|
||||
after = [ "acme-${domain}.service" ];
|
||||
};
|
||||
|
||||
users.groups.acme.members = [ "openldap" ];
|
||||
|
||||
/* trigger the actual certificate generation for your hostname */
|
||||
security.acme.certs."ldap.${domain}" = {
|
||||
extraDomainNames = [
|
||||
"ldap-test.${domain}"
|
||||
"ldap-02.${domain}"
|
||||
];
|
||||
postRun = "systemctl restart openldap.service";
|
||||
};
|
||||
|
||||
sops.secrets.openldap-rootpw.owner = "openldap";
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 389 636 ];
|
||||
}
|
||||
246
hosts/mail.social-grow.tech/modules/postfix.nix
Normal file
246
hosts/mail.social-grow.tech/modules/postfix.nix
Normal file
@@ -0,0 +1,246 @@
|
||||
{ pkgs
|
||||
, lib
|
||||
, config
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
domain = config.networking.domain;
|
||||
ldapServer = "ldap.cloonar.com";
|
||||
# domain = "cloonar.com";
|
||||
|
||||
domains = pkgs.writeText "domains.cf" ''
|
||||
server_host = ldap://${ldapServer}
|
||||
search_base = ou=domains,dc=cloonar,dc=com
|
||||
version = 3
|
||||
bind = yes
|
||||
start_tls = yes
|
||||
bind_dn = cn=vmail,ou=system,ou=users,dc=cloonar,dc=com
|
||||
bind_pw = @ldap-password@
|
||||
scope = one
|
||||
query_filter = (&(dc=%s)(objectClass=mailDomain))
|
||||
result_attribute = postfixTransport
|
||||
debuglevel = 0
|
||||
'';
|
||||
|
||||
mailboxes = pkgs.writeText "mailboxes.cf" ''
|
||||
server_host = ldap://${ldapServer}
|
||||
search_base = ou=users,dc=%2,dc=%1
|
||||
version = 3
|
||||
bind = yes
|
||||
start_tls = yes
|
||||
bind_dn = cn=vmail,ou=system,ou=users,dc=cloonar,dc=com
|
||||
bind_pw = @ldap-password@
|
||||
scope = sub
|
||||
query_filter = (&(uid=%u)(objectClass=mailAccount))
|
||||
result_attribute = mail
|
||||
debuglevel = 0
|
||||
'';
|
||||
|
||||
senderLoginMaps = pkgs.writeText "sender_login_maps.cf" ''
|
||||
server_host = ldap://${ldapServer}
|
||||
search_base = dc=%2,dc=%1
|
||||
version = 3
|
||||
bind = yes
|
||||
start_tls = yes
|
||||
bind_dn = cn=vmail,ou=system,ou=users,dc=cloonar,dc=com
|
||||
bind_pw = @ldap-password@
|
||||
scope = sub
|
||||
query_filter = (|(&(objectClass=mailAccount)(uid=%u))(&(objectClass=mailAlias)(mail=%s)))
|
||||
result_attribute = maildrop, mail
|
||||
debuglevel = 0
|
||||
'';
|
||||
|
||||
accountsmap = pkgs.writeText "accountsmap.cf" ''
|
||||
server_host = ldap://${ldapServer}
|
||||
search_base = ou=users,dc=%2,dc=%1
|
||||
version = 3
|
||||
bind = yes
|
||||
start_tls = yes
|
||||
bind_dn = cn=vmail,ou=system,ou=users,dc=cloonar,dc=com
|
||||
bind_pw = @ldap-password@
|
||||
scope = sub
|
||||
query_filter = (&(objectClass=mailAccount)(uid=%u))
|
||||
result_attribute = mail
|
||||
debuglevel = 0
|
||||
'';
|
||||
|
||||
aliases = pkgs.writeText "aliases.cf" ''
|
||||
server_host = ldap://${ldapServer}
|
||||
search_base = ou=aliases,dc=%2,dc=%1
|
||||
version = 3
|
||||
bind = yes
|
||||
start_tls = yes
|
||||
bind_dn = cn=vmail,ou=system,ou=users,dc=cloonar,dc=com
|
||||
bind_pw = @ldap-password@
|
||||
scope = one
|
||||
query_filter = (&(objectClass=mailAlias)(mail=%s))
|
||||
result_attribute = maildrop
|
||||
debuglevel = 0
|
||||
'';
|
||||
|
||||
helo_access = pkgs.writeText "helo_access" ''
|
||||
/^([0-9\.]+)$/ REJECT ACCESS DENIED. Your email was rejected because the sending mail server sent non RFC compliant HELO identity (''${1})
|
||||
cloonar.com REJECT ACCESS DENIED. Your email was rejected because the sending mail server sent non RFC compliant HELO identity (''${1})
|
||||
ghetto.at REJECT ACCESS DENIED. Your email was rejected because the sending mail server sent non RFC compliant HELO identity (''${1})
|
||||
'';
|
||||
in
|
||||
{
|
||||
services.postfix = {
|
||||
enable = true;
|
||||
enableSubmission = true;
|
||||
hostname = "mail.${domain}";
|
||||
domain = "cloonar.com";
|
||||
|
||||
masterConfig."465" = {
|
||||
type = "inet";
|
||||
private = false;
|
||||
command = "smtpd";
|
||||
args = [
|
||||
"-o smtpd_client_restrictions=permit_sasl_authenticated,reject"
|
||||
"-o syslog_name=postfix/smtps"
|
||||
"-o smtpd_tls_wrappermode=yes"
|
||||
"-o smtpd_sasl_auth_enable=yes"
|
||||
"-o smtpd_tls_security_level=none"
|
||||
"-o smtpd_reject_unlisted_recipient=no"
|
||||
"-o smtpd_recipient_restrictions="
|
||||
"-o smtpd_relay_restrictions=permit_sasl_authenticated,reject"
|
||||
"-o milter_macro_daemon_name=ORIGINATING"
|
||||
];
|
||||
};
|
||||
|
||||
mapFiles."helo_access" = helo_access;
|
||||
|
||||
config = {
|
||||
# debug_peer_list = "10.42.96.190";
|
||||
# smtp_bind_address = config.networking.eve.ipv4.address;
|
||||
# smtp_bind_address6 = "2a01:4f9:2b:1605::1";
|
||||
mailbox_transport = "lmtp:unix:private/dovecot-lmtp";
|
||||
virtual_mailbox_domains = "ldap:/run/postfix/domains.cf";
|
||||
virtual_mailbox_maps = "ldap:/run/postfix/mailboxes.cf";
|
||||
virtual_alias_maps = "ldap:/run/postfix/accountsmap.cf,ldap:/run/postfix/aliases.cf";
|
||||
virtual_transport = "lmtp:unix:private/dovecot-lmtp";
|
||||
smtpd_sender_login_maps = "ldap:/run/postfix/sender_login_maps.cf";
|
||||
|
||||
# Do not display the name of the recipient table in the "User unknown" responses.
|
||||
# The extra detail makes trouble shooting easier but also reveals information
|
||||
# that is nobody elses business.
|
||||
show_user_unknown_table_name = "no";
|
||||
compatibility_level = "2";
|
||||
|
||||
# bigger attachement size
|
||||
mailbox_size_limit = "202400000";
|
||||
message_size_limit = "51200000";
|
||||
smtpd_helo_required = "yes";
|
||||
smtpd_delay_reject = "yes";
|
||||
strict_rfc821_envelopes = "yes";
|
||||
|
||||
# send Limit
|
||||
smtpd_error_sleep_time = "1s";
|
||||
smtpd_soft_error_limit = "10";
|
||||
smtpd_hard_error_limit = "20";
|
||||
|
||||
smtpd_use_tls = "yes";
|
||||
smtp_tls_note_starttls_offer = "yes";
|
||||
smtpd_tls_security_level = "may";
|
||||
smtpd_tls_auth_only = "yes";
|
||||
|
||||
smtp_dns_support_level = "dnssec";
|
||||
smtp_tls_security_level = "dane";
|
||||
|
||||
smtpd_tls_cert_file = "/var/lib/acme/mail.cloonar.com/full.pem";
|
||||
smtpd_tls_key_file = "/var/lib/acme/mail.cloonar.com/key.pem";
|
||||
smtpd_tls_CAfile = "/var/lib/acme/mail.cloonar.com/fullchain.pem";
|
||||
|
||||
smtpd_tls_dh512_param_file = config.security.dhparams.params.postfix512.path;
|
||||
smtpd_tls_dh1024_param_file = config.security.dhparams.params.postfix2048.path;
|
||||
|
||||
smtpd_tls_session_cache_database = ''btree:''${data_directory}/smtpd_scache'';
|
||||
smtpd_tls_mandatory_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1";
|
||||
smtpd_tls_protocols = "!SSLv2,!SSLv3,!TLSv1,!TLSv1.1";
|
||||
smtpd_tls_mandatory_ciphers = "medium";
|
||||
tls_medium_cipherlist = "AES128+EECDH:AES128+EDH";
|
||||
|
||||
# authentication
|
||||
smtpd_sasl_auth_enable = "yes";
|
||||
smtpd_sasl_local_domain = "$mydomain";
|
||||
smtpd_sasl_security_options = "noanonymous";
|
||||
smtpd_sasl_tls_security_options = "$smtpd_sasl_security_options";
|
||||
smtpd_sasl_type = "dovecot";
|
||||
smtpd_sasl_path = "/var/lib/postfix/queue/private/auth";
|
||||
smtpd_relay_restrictions = "
|
||||
permit_mynetworks,
|
||||
permit_sasl_authenticated,
|
||||
defer_unauth_destination";
|
||||
smtpd_client_restrictions = "
|
||||
permit_mynetworks,
|
||||
permit_sasl_authenticated,
|
||||
reject_invalid_hostname,
|
||||
reject_unknown_client,
|
||||
permit";
|
||||
smtpd_helo_restrictions = "
|
||||
permit_mynetworks,
|
||||
permit_sasl_authenticated,
|
||||
reject_unauth_pipelining,
|
||||
reject_non_fqdn_hostname,
|
||||
reject_invalid_hostname,
|
||||
warn_if_reject reject_unknown_hostname,
|
||||
permit";
|
||||
smtpd_recipient_restrictions = "
|
||||
permit_mynetworks,
|
||||
permit_sasl_authenticated,
|
||||
reject_non_fqdn_sender,
|
||||
reject_non_fqdn_recipient,
|
||||
reject_non_fqdn_hostname,
|
||||
reject_invalid_hostname,
|
||||
reject_unknown_sender_domain,
|
||||
reject_unknown_recipient_domain,
|
||||
reject_unknown_client_hostname,
|
||||
reject_unauth_pipelining,
|
||||
reject_unknown_client,
|
||||
permit";
|
||||
smtpd_sender_restrictions = "
|
||||
reject_non_fqdn_sender,
|
||||
reject_unlisted_sender,
|
||||
reject_authenticated_sender_login_mismatch,
|
||||
permit_mynetworks,
|
||||
permit_sasl_authenticated,
|
||||
reject_unknown_sender_domain,
|
||||
reject_unknown_client_hostname,
|
||||
reject_unknown_address";
|
||||
|
||||
smtpd_etrn_restrictions = "permit_mynetworks, reject";
|
||||
smtpd_data_restrictions = "reject_unauth_pipelining, reject_multi_recipient_bounce, permit";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [ "d /run/postfix 0750 postfix postfix -" ];
|
||||
|
||||
systemd.services.postfix.preStart = ''
|
||||
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${domains} > /run/postfix/domains.cf
|
||||
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${mailboxes} > /run/postfix/mailboxes.cf
|
||||
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${accountsmap} > /run/postfix/accountsmap.cf
|
||||
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${aliases} > /run/postfix/aliases.cf
|
||||
sed -e "s/@ldap-password@/$(cat ${config.sops.secrets.dovecot-ldap-password.path})/" ${senderLoginMaps} > /run/postfix/sender_login_maps.cf
|
||||
'';
|
||||
|
||||
security.dhparams = {
|
||||
enable = true;
|
||||
params.postfix512.bits = 512;
|
||||
params.postfix2048.bits = 1024;
|
||||
};
|
||||
|
||||
security.acme.certs."mail.${domain}" = {
|
||||
extraDomainNames = [
|
||||
"mail-test.${domain}"
|
||||
"mail-02.${domain}"
|
||||
];
|
||||
postRun = "systemctl restart postfix.service";
|
||||
group = "postfix";
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
25 # smtp
|
||||
465 # smtps
|
||||
587 # submission
|
||||
];
|
||||
}
|
||||
131
hosts/mail.social-grow.tech/modules/rspamd.nix
Normal file
131
hosts/mail.social-grow.tech/modules/rspamd.nix
Normal file
@@ -0,0 +1,131 @@
|
||||
{ pkgs
|
||||
, config
|
||||
, ...
|
||||
}:
|
||||
let
|
||||
domain = config.networking.domain;
|
||||
|
||||
localConfig = pkgs.writeText "local.conf" ''
|
||||
logging {
|
||||
level = "notice";
|
||||
}
|
||||
classifier "bayes" {
|
||||
autolearn = true;
|
||||
}
|
||||
dkim_signing {
|
||||
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
|
||||
selector = "default";
|
||||
allow_username_mismatch = true;
|
||||
}
|
||||
arc {
|
||||
path = "/var/lib/rspamd/dkim/$domain.$selector.key";
|
||||
selector = "default";
|
||||
allow_username_mismatch = true;
|
||||
}
|
||||
milter_headers {
|
||||
use = ["authentication-results", "x-spam-status"];
|
||||
authenticated_headers = ["authentication-results"];
|
||||
}
|
||||
replies {
|
||||
action = "no action";
|
||||
}
|
||||
url_reputation {
|
||||
enabled = true;
|
||||
}
|
||||
phishing {
|
||||
openphish_enabled = true;
|
||||
# too much memory
|
||||
#phishtank_enabled = true;
|
||||
}
|
||||
neural {
|
||||
enabled = true;
|
||||
}
|
||||
neural_group {
|
||||
symbols = {
|
||||
"NEURAL_SPAM" {
|
||||
weight = 3.0; # sample weight
|
||||
description = "Neural network spam";
|
||||
}
|
||||
"NEURAL_HAM" {
|
||||
weight = -3.0; # sample weight
|
||||
description = "Neural network ham";
|
||||
}
|
||||
}
|
||||
}
|
||||
'';
|
||||
|
||||
sieve-spam-filter = pkgs.callPackage ../pkgs/sieve-spam-filter { };
|
||||
in
|
||||
{
|
||||
services.rspamd = {
|
||||
enable = true;
|
||||
extraConfig = ''
|
||||
.include(priority=1,duplicate=merge) "${localConfig}"
|
||||
'';
|
||||
|
||||
postfix.enable = true;
|
||||
workers.controller = {
|
||||
extraConfig = ''
|
||||
count = 1;
|
||||
static_dir = "''${WWWDIR}";
|
||||
password = "$2$7rb4gnnw8qbcy3x3m7au8c4mezecfjim$da4ahtt3gnjtbj7ni6bt1q8jwgqtzxp5ck6941m6prjxsz3udfgb";
|
||||
enable_password = "$2$xo1qdd1zgozwto8yazr1o35zbarbzcgp$u8mx6hcsb1qdscejb4zadcb3iucmm4mw6btgmim9h6e5d8cpy5ib";
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
services.dovecot2 = {
|
||||
mailboxes.Spam = {
|
||||
auto = "subscribe";
|
||||
specialUse = "Junk";
|
||||
};
|
||||
extraConfig = ''
|
||||
protocol imap {
|
||||
mail_plugins = $mail_plugins imap_sieve
|
||||
}
|
||||
|
||||
plugin {
|
||||
sieve_plugins = sieve_imapsieve sieve_extprograms
|
||||
|
||||
# From elsewhere to Spam folder
|
||||
imapsieve_mailbox1_name = Spam
|
||||
imapsieve_mailbox1_causes = COPY
|
||||
imapsieve_mailbox1_before = file:/var/lib/dovecot/sieve/report-spam.sieve
|
||||
|
||||
# From Spam folder to elsewhere
|
||||
imapsieve_mailbox2_name = *
|
||||
imapsieve_mailbox2_from = Spam
|
||||
imapsieve_mailbox2_causes = COPY
|
||||
imapsieve_mailbox2_before = file:/var/lib/dovecot/sieve/report-ham.sieve
|
||||
|
||||
# Move Spam emails to Spam folder
|
||||
sieve_before = /var/lib/dovecot/sieve/move-to-spam.sieve
|
||||
|
||||
sieve_pipe_bin_dir = ${sieve-spam-filter}/bin
|
||||
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
|
||||
}
|
||||
'';
|
||||
};
|
||||
|
||||
services.nginx.enable = true;
|
||||
services.nginx.virtualHosts."rspamd.${domain}" = {
|
||||
forceSSL = true;
|
||||
enableACME = true;
|
||||
acmeRoot = null;
|
||||
locations."/".extraConfig = ''
|
||||
proxy_pass http://localhost:11334;
|
||||
'';
|
||||
};
|
||||
|
||||
# systemd.services.rspamd.serviceConfig.SupplementaryGroups = [ "redis-rspamd" ];
|
||||
|
||||
systemd.services.dovecot2.preStart = ''
|
||||
mkdir -p /var/lib/dovecot/sieve/
|
||||
for i in ${sieve-spam-filter}/share/sieve-rspamd-filter/*.sieve; do
|
||||
dest="/var/lib/dovecot/sieve/$(basename $i)"
|
||||
cp "$i" "$dest"
|
||||
${pkgs.dovecot_pigeonhole}/bin/sievec "$dest"
|
||||
done
|
||||
chown -R "${config.services.dovecot2.mailUser}:${config.services.dovecot2.mailGroup}" /var/lib/dovecot/sieve
|
||||
'';
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{ stdenv
|
||||
, makeWrapper
|
||||
, rspamd
|
||||
,
|
||||
}:
|
||||
stdenv.mkDerivation {
|
||||
name = "sieve-rspamd-filter";
|
||||
nativeBuildInputs = [ makeWrapper ];
|
||||
src = ./src;
|
||||
|
||||
installPhase = ''
|
||||
for sieve in $src/*.sieve; do
|
||||
install -D "$sieve" "$out/share/sieve-rspamd-filter/$(basename $sieve)"
|
||||
done
|
||||
|
||||
mkdir $out/bin
|
||||
cat > $out/bin/learn-spam.sh <<'EOF'
|
||||
#!/bin/sh
|
||||
exec ${rspamd}/bin/rspamc -h /run/rspamd.sock learn_spam
|
||||
EOF
|
||||
cat > $out/bin/learn-ham.sh <<'EOF'
|
||||
#!/bin/sh
|
||||
exec ${rspamd}/bin/rspamc -h /run/rspamd.sock learn_ham
|
||||
EOF
|
||||
chmod +x $out/bin/*.sh
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
require ["fileinto"];
|
||||
|
||||
if header :is "X-Spam" "Yes" {
|
||||
fileinto "Spam";
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||
|
||||
if environment :matches "imap.mailbox" "*" {
|
||||
set "mailbox" "${1}";
|
||||
}
|
||||
|
||||
if string "${mailbox}" "Trash" {
|
||||
stop;
|
||||
}
|
||||
|
||||
if environment :matches "imap.user" "*" {
|
||||
set "username" "${1}";
|
||||
}
|
||||
|
||||
pipe :copy "learn-ham.sh" [ "${username}" ];
|
||||
@@ -0,0 +1,7 @@
|
||||
require ["vnd.dovecot.pipe", "copy", "imapsieve", "environment", "variables"];
|
||||
|
||||
if environment :matches "imap.user" "*" {
|
||||
set "username" "${1}";
|
||||
}
|
||||
|
||||
pipe :copy "learn-spam.sh" [ "${username}" ];
|
||||
52
hosts/mail.social-grow.tech/secrets.yaml
Normal file
52
hosts/mail.social-grow.tech/secrets.yaml
Normal file
@@ -0,0 +1,52 @@
|
||||
borg-passphrase: ENC[AES256_GCM,data:D6+ZedxUQ7m/m0YkM5m/B4kFsNySJjFyh8Gmhn3Mpe+mqEzzMRjAbwmGzx9i9Lnr1dTjRElUOgevnnvW5J2KRA==,iv:cG4w1KsEm1SOTni9bsbSW1+ypzjjs2Q42I+4xvcCAu0=,tag:WkkNVa27Uy5nFpmXaIH6ww==,type:str]
|
||||
borg-ssh-key: ENC[AES256_GCM,data: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,iv:yA1CkRMapP1S3zMwu6Tj0/0/HHpwD1yRAm/qrZx/kPs=,tag:SYg2IoXeD9fMYb35J/AJ1Q==,type:str]
|
||||
netdata-claim-token: ENC[AES256_GCM,data:ECx8zLnU/dj08vfA76oVbVzL3JG9MLBoFmxSjtjiFbSiFtdaHtG/8u5FEuyQ1bQMQntV91xj7x1kY8fAp7VNbWyC13pOEOrt6rvJYch14eM3bqNvfGeqgJsHmAaRbY6mBrxJBkiRJBLYVil4e1oDNZVnzFQ4ditXZbMGtAV2063K1MRI/48p,iv:viE84mOp5KSdj8vdK5XxR0W9A54oPxQO5ahnpPLeAdE=,tag:WjzKjGXRRAc7vlzreFHbng==,type:str]
|
||||
openldap-rootpw: ENC[AES256_GCM,data:W0em1Dffg+IUoynwwPD4NjFksR38ZO4mhWFI83ALvYcwYIplxw/gDRLGCqbSt6TR5C65CKr1sOUiU+4Xq3UWmw==,iv:BHQhISTIYuwSM3KiSb0mEEo3BMNo6FXEDXoIvI3SZrU=,tag:tX8gfnk1JYnaNionk/jrLg==,type:str]
|
||||
dovecot-ldap-password: ENC[AES256_GCM,data:JYAt8/WggwclNEPO9CaWfQsvQBA8DDJCU2km93HpowoVwIdvQ/0lQHeXndPYe1EmJGJ3vLErie+Zn2kDINIMqQ==,iv:HR0QJ0GgQks3NzhfXwjHupCKcPOekkiTcp5Jxbz7CxI=,tag:19m7F6TjGUPOuHQJuUq2pw==,type:str]
|
||||
sops:
|
||||
kms: []
|
||||
gcp_kms: []
|
||||
azure_kv: []
|
||||
hc_vault: []
|
||||
age:
|
||||
- recipient: age16veg3fmvpfm7a89a9fc8dvvsxmsthlm70nfxqspr6t8vnf9wkcwsvdq38d
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBPRWdBcmEvQkQrOXZ0SDJW
|
||||
eFpFSlBxbjlUbFlDVEZzS3dLSXN6MnBFT3lnCkZ1RGhoQjhtcGxEY1E1QlBvNUl0
|
||||
RWxnbzNldHBHUjhiZldYQm9iYWppcncKLS0tIG12WFdYSVdDYVZUaEFzUFhJS3A2
|
||||
Q0I2b2h4aFlkNkV1a1BFamhyd0ZBWTgKZwxpdydc1lgs3u9gkh2Krs8PGfcKwJTv
|
||||
n7BV0FNa242wOT4Tu28O9SN7VR1zZR52iOgV7gWsCnhkNDk9kwiLHA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1v6p8dan2t3w9h94fz4flldl32082j3s9x6zqq7u5j66keth9aphsd6pvch
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBoSHFtMUczc0tXaDZoQllM
|
||||
eHFpYTFmcnpyYitwT1U2eGNuQm5MQms3YUdJCmpVS2hOVjFmUlVUZy9MZTZxQVlq
|
||||
SU8xcmd2a0tvWlBMc2M1Wm5XV3ZQZTAKLS0tIG9qa2pQbDFIbFArejM1d1VRRVFY
|
||||
VjJwdC8yQ1hweEllcGhYclNwTWFyZ1UKDKv14nnVx3FeL87FYFqZMU+niHBOvxHz
|
||||
3L3hBMEgpR/uMSuPmF4/NLVJTsktOonW9NKOzm37KsY2HNRXbuHoQg==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1jyeppc8yl2twnv8fwcewutd5gjewnxl59lmhev6ygds9qel8zf8syt7zz4
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBjY2JOWTA0a3pGL0dYc2t4
|
||||
aE8vTUNMNDVML2ZOSW9xeHlFRDQ5K1BLR3l3ClN4a25QZTEzaFk5bnVUYkk2dnRr
|
||||
SWxNTklrZGM4enJ0WXBKaEJ6UDZUMzAKLS0tIDJudGtSVTVTV3ZrWWh6VnZFdEs3
|
||||
UFVlWE9wd3hRS0d3VEg5di9kNHBIeUEKov+NZ0pt4BUd5xXX9cTFSJF355Kg0ios
|
||||
Va/kbzgG2SMvxMorNFDp+yJgGXM9rOycMJ1ajemKBM3r2QMcsIiMWA==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
- recipient: age1azmxsw5llmp2nnsv3yc2l8paelmq9rfepxd8jvmswgsmax0qyyxqdnsc7t
|
||||
enc: |
|
||||
-----BEGIN AGE ENCRYPTED FILE-----
|
||||
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJVVRBY1RVdmdkTGxkT3N0
|
||||
YjJUdXU5blY3T1R2NFQwQ2MvUitTRjZOUGpjCkNMTUJOaCtGR0s4SGxENXRRd1lQ
|
||||
cE9RbFUvL1RVZnZ1a3RlZ0YxbmFtOGsKLS0tIE8vMmE1YkZCM210SXEzRFZJeWZL
|
||||
eC80bWxndE85RlZGRUFTcDdaZ2J1VE0KZ0FERlT1kdUE+WxSi57YowqDQtA9BoV1
|
||||
MZoPePwGkRr27MHnPYIhoniUXC7mhQ4rqvcbFy6i1n4r1CqkRFBM3g==
|
||||
-----END AGE ENCRYPTED FILE-----
|
||||
lastmodified: "2024-07-08T11:20:50Z"
|
||||
mac: ENC[AES256_GCM,data:GPUwpSAz6fj7mRxX1ebEb2sLAMLkQLuKPXk+B3+zZmA6+D7gAKrrBGUWHqYA9DMMY0r32OZSccGRmeKqdA7sWmzdIJTcBu8EyER1nJqVFJiXcOOdTkCLdOM4xW969YE0lBKpIAQ40E7YXYYwkI1JINneIBTuXkvIBmSQ3Bt2+ak=,iv:VEPNQxDLzxyTxkn8dI6xNDe9ESk2RojSNYYEwT+Ggas=,tag:cfUEKU3arSJl+lEOa+4iRA==,type:str]
|
||||
pgp: []
|
||||
unencrypted_suffix: _unencrypted
|
||||
version: 3.8.1
|
||||
1
hosts/mail.social-grow.tech/utils
Symbolic link
1
hosts/mail.social-grow.tech/utils
Symbolic link
@@ -0,0 +1 @@
|
||||
../../utils
|
||||
Reference in New Issue
Block a user