commit 7226c379cce4e199b809b3abe238490d57663ee8
parent f73bde980edd51a5068a1cc0bea0d133d7996469
Author: Leah (ctucx) <git@ctu.cx>
Date: Sat, 26 Nov 2022 21:32:54 +0100
parent f73bde980edd51a5068a1cc0bea0d133d7996469
Author: Leah (ctucx) <git@ctu.cx>
Date: Sat, 26 Nov 2022 21:32:54 +0100
machines/osterei/mail: move to machine `trabbi`
15 files changed, 556 insertions(+), 528 deletions(-)
D
|
355
-------------------------------------------------------------------------------
D
|
129
-------------------------------------------------------------------------------
A
|
359
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
|
158
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/machines/osterei/configuration.nix b/machines/osterei/configuration.nix @@ -13,10 +13,6 @@ # monitoring ../../configurations/linux/services/prometheus-node-exporter.nix - - # communication - ./mail.nix - ]; services.pcscd.enable = lib.mkForce false;
diff --git a/machines/osterei/maddy.nix b/machines/osterei/maddy.nix @@ -1,355 +0,0 @@ -{ inputs, config, lib, pkgs, ... }: - -let - mailboxFilterScript = pkgs.writePythonScriptBin "mailbox-filter.py" (ps: [ ps.toml ps.mail-parser ]) '' - from email.header import Header, decode_header, make_header - import sys, re - import toml, mailparser - - def filter_mail(config, sender, recipient, subject): - for type in [ 'recipient', 'subject', 'sender' ]: - if type not in config: - continue - - for key, value in config[type].items(): - if(re.search("^" + key + "$", str(eval(type)))): - print(value.replace(",", "\n")) - sys.exit(0) - - try: - account_name = sys.argv[1] - config = toml.load('/etc/maddy/filters/mailbox/' + account_name + '.toml') - - if len(sys.argv) > 2: - filter_mail(config, sys.argv[2], sys.argv[3], make_header(decode_header(sys.argv[4]))) - else: - sender = subject = "" - message = mailparser.parse_from_string(sys.stdin.read()) - - if len(message.from_) > 0: - if len(message.from_[0]) == 2: - sender = message.from_[0][1] - - if message.subject is not None: - subject = message.subject - - for recipient in message.to: - filter_mail(config, sender, recipient[1], subject) - - except: - pass - - sys.exit(0) - ''; - - receiveFilterScript = pkgs.writePythonScriptBin "receive-filter.py" (ps: [ ps.toml ]) '' - import sys, toml - - try: - sender = sys.argv[1] - recipient = sys.argv[2] - config = toml.load('/etc/maddy/filters/receive.toml') - - for type in [ 'recipient', 'sender' ]: - if type not in config: - continue - - if 'reject' in config[type]: - if eval(type) in config[type]['reject']: - sys.exit(10) - - if 'quarantine' in config[type]: - if eval(type) in config[type]['quarantine']: - sys.exit(20) - - except SystemExit as e: - sys.exit(e) - - except: - pass - - sys.exit(0) - ''; - -in { - - environment.etc."maddy/filters/mailbox/leah@ctu.cx.toml".text = "${inputs.nix-std.lib.serde.toTOML inputs.local-secrets.maddy.mailboxFilter}"; - environment.etc."maddy/filters/receive.toml".text = "${inputs.nix-std.lib.serde.toTOML inputs.local-secrets.maddy.receiveFilter}"; - - security.acme.certs."osterei.ctu.cx".reloadServices = [ "maddy.service" ]; - - systemd.services.maddy.serviceConfig.ReadOnlyPaths = [ "/etc/maddy/filters" ]; - - age.secrets.restic-maddy.file = ../../secrets/osterei/restic/maddy.age; - - networking.firewall.allowedTCPPorts = [ 25 143 465 587 993 ]; - - dns.zones = with pkgs.dns.lib.combinators; let - TXT = [ "v=spf1 a mx ip4:185.232.70.80 +ip6:2a03:4000:4e:af1::1 ~all" ]; - DMARC = "v=DMARC1; p=none"; - MX = with mx; [ (mx 10 "osterei.ctu.cx.") ]; - in { - "ctu.cx" = { - inherit MX TXT; - - SRV = [ - { proto = "tcp"; service = "imaps"; priority = 0; weight = 1; port = 993; target = "osterei.ctu.cx."; } - { proto = "tcp"; service = "imap"; priority = 0; weight = 1; port = 143; target = "osterei.ctu.cx."; } - { proto = "tcp"; service = "submission"; priority = 0; weight = 1; port = 587; target = "osterei.ctu.cx."; } - ]; - - subdomains = { - _dmarc.TXT = [ DMARC ]; - "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=nWRKCHE19fL1RHJ2cVkC8Xvfzm9OtgeF5VC2lD+EaEo=" ]; - }; - }; - - "ctucx.de" = { - inherit MX TXT; - - subdomains = { - _dmarc.TXT = [ DMARC ]; - "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=U9JMZlv7BpLXGIpO7WdJ/7ephxwJtJ02jaVUUadyP9s" ]; - }; - }; - - "thein.ovh" = { - inherit MX TXT; - - subdomains = { - _dmarc.TXT = [ DMARC ]; - "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=KYkebiXYSc/+7Rtdz/ZZFRAXAsQnyLPYA6r2uboh5oc=" ]; - }; - }; - - "zug.network" = { - inherit MX TXT; - - subdomains = { - _dmarc.TXT = [ DMARC ]; - "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=dH1h2nvPlmT0lIrGddpYRZFTm0AD6D+fsU36McEso2g=" ]; - }; - }; - }; - - users.groups.maddy = {}; - users.users.maddy = { - isSystemUser = true; - home = "/var/lib/maddy"; - group = "maddy"; - extraGroups = [ "nginx" ]; - }; - - restic-backups.maddy = { - user = "maddy"; - passwordFile = config.age.secrets.restic-maddy.path; - paths = [ "/var/lib/maddy" ]; - }; - - services.maddy = { - enable = true; - user = "maddy"; - group = "maddy"; - hostname = "osterei.ctu.cx"; - primaryDomain = "ctu.cx"; - localDomains = [ - "$(hostname)" - "$(primary_domain)" - "thein.ovh" - "ctucx.de" - "trans-agenda.de" - "zug.network" - ]; - - config = '' - #debug on - log syslog - - tls file /var/lib/acme/osterei.ctu.cx/fullchain.pem /var/lib/acme/osterei.ctu.cx/key.pem - - # ---------------------------------------------------------------------------- - # Local storage & authentication - - # pass_table provides local hashed passwords storage for authentication of - # users. It can be configured to use any "table" module, in default - # configuration a table in SQLite DB is used. - # Table can be replaced to use e.g. a file for passwords. Or pass_table module - # can be replaced altogether to use some external source of credentials (e.g. - # PAM, /etc/shadow file). - # - # If table module supports it (sql_table does) - credentials can be managed - # using 'maddyctl creds' command. - - auth.pass_table local_authdb { - table sql_table { - driver sqlite3 - dsn credentials.db - table_name passwords - } - } - - # imapsql module stores all indexes and metadata necessary for IMAP using a - # relational database. It is used by IMAP endpoint for mailbox access and - # also by SMTP & Submission endpoints for delivery of local messages. - # - # IMAP accounts, mailboxes and all message metadata can be inspected using - # imap-* subcommands of maddyctl utility. - - storage.imapsql local_mailboxes { - driver sqlite3 - dsn imapsql.db - disable_recent false - compression zstd - - imap_filter { - command ${mailboxFilterScript} {account_name} - } - } - - # ---------------------------------------------------------------------------- - # SMTP endpoints + message routing - - msgpipeline local_routing { - dmarc yes - check { - require_mx_record - dkim - spf { - permerr_action ignore - } - command ${receiveFilterScript} {sender} {rcpts} { - run_on rcpt - - code 1 ignore - code 2 ignore - code 10 reject - code 20 quarantine - } - } - - destination postmaster $(local_domains) { - modify { - replace_rcpt static { - entry postmaster postmaster@$(primary_domain) - entry leon@thein.ovh leah@ctu.cx - entry aufsicht@zug.network mail@zug.network - entry verwaltung@zug.network mail@zug.network - } - - # Implement plus-address notation. - replace_rcpt regexp "(.+)\+(.+)@(.+)" "$1@$3" - - replace_rcpt regexp "(.+)@ctucx.de" "leah@ctu.cx" - replace_rcpt regexp "(.+)@ctu.cx" "leah@ctu.cx" - } - - deliver_to &local_mailboxes - } - - default_destination { - reject 550 5.1.1 "User doesn't exist" - } - } - - smtp tcp://0.0.0.0:25 { - limits { - # Up to 20 msgs/sec across max. 10 SMTP connections. - all rate 20 1s - all concurrency 10 - } - - source $(local_domains) { - reject 501 5.1.8 "Use Submission for outgoing SMTP" - } - - default_source { - destination postmaster $(local_domains) { - deliver_to &local_routing - } - - default_destination { - reject 550 5.1.1 "User doesn't exist" - } - } - } - - submission tls://0.0.0.0:465 tcp://0.0.0.0:587 { - limits { - # Up to 50 msgs/sec across any amount of SMTP connections. - all rate 50 1s - } - - auth &local_authdb - - source $(local_domains) { - destination postmaster $(local_domains) { - deliver_to &local_routing - } - - default_destination { - modify { - dkim $(primary_domain) $(local_domains) default { - newkey_algo ed25519 - } - } - deliver_to &remote_queue - } - } - - default_source { - reject 501 5.1.8 "Non-local sender domain" - } - } - - target.remote outbound_delivery { - limits { - # Up to 20 msgs/sec across max. 10 SMTP connections - # for each recipient domain. - destination rate 20 1s - destination concurrency 10 - } - - mx_auth { - dane - - mtasts { - cache fs - fs_dir mtasts_cache/ - } - - local_policy { - min_tls_level encrypted - min_mx_level none - } - } - } - - target.queue remote_queue { - target &outbound_delivery - max_parallelism 16 - max_tries 4 - - autogenerated_msg_domain $(primary_domain) - - bounce { - destination postmaster $(local_domains) { - deliver_to &local_routing - } - - default_destination { - reject 550 5.0.0 "Refusing to send DSNs to non-local addresses" - } - } - } - - # ---------------------------------------------------------------------------- - # IMAP endpoints - - imap tls://0.0.0.0:993 tcp://0.0.0.0:143 { - auth &local_authdb - storage &local_mailboxes - } - ''; - }; - -}
diff --git a/machines/osterei/mail.nix b/machines/osterei/mail.nix @@ -1,129 +0,0 @@ -{ inputs, pkgs, config, ... }: - -{ - - imports = [ - inputs.simple-nixos-mailserver.nixosModule - ]; - - age.secrets.restic-mail.file = ../../secrets/osterei/restic/mail.age; - age.secrets.mail-password-leah.file = ../../secrets/osterei/mail/password-leah-ctu.cx.age; - age.secrets.mail-password-zugnetwork.file = ../../secrets/osterei/mail/password-mail-zug.network.age; - - dns.zones = with pkgs.dns.lib.combinators; let - TXT = [ "v=spf1 a mx ip4:185.232.70.80 +ip6:2a03:4000:4e:af1::1 ~all" ]; - DMARC = "v=DMARC1; p=none"; - MX = with mx; [ (mx 10 "osterei.ctu.cx.") ]; - in { - "ctu.cx" = { - inherit MX TXT; - - SRV = [ - { proto = "tcp"; service = "imaps"; priority = 0; weight = 1; port = 993; target = "osterei.ctu.cx."; } - { proto = "tcp"; service = "imap"; priority = 0; weight = 1; port = 143; target = "osterei.ctu.cx."; } - { proto = "tcp"; service = "submission"; priority = 0; weight = 1; port = 587; target = "osterei.ctu.cx."; } - ]; - - subdomains = { - _dmarc.TXT = [ DMARC ]; - "mail._domainkey".TXT = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKryfX99NkcU5Xe4AmG+kO/sfuYSXk5RqJhzxS4uMqERE8UszgEGdteXcD8pqON2MfDmA3G6cA+Oa+N4tIWdIYNwTISVXXMGdHvjFIsVUEW0turM104tXESELaPRntkCvDBk/yOgsBDRZQHSx5MdGwpzeRC8TLdCbalh3W0jp5PQIDAQA" ]; - }; - }; - - "ctucx.de" = { - inherit MX TXT; - - subdomains = { - _dmarc.TXT = [ DMARC ]; - "mail._domainkey".TXT = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5fu690bKYCZLPAFfQQK+nl+aAmtetaWBKCWzGj6pt7HjpFjystgtgnQ6+DZLFXWUp8GRfMEycySB5kQULtYtSMUmx0gQBnTTLsRj+e55/CYUllLV6YXb5uca7LuVhlWPpH3sCr6TvC2VFWe4t0UC3uIXhYPrCm6p8OE7g+TdHHwIDAQAB" ]; - }; - }; - - "thein.ovh" = { - inherit MX TXT; - - subdomains = { - _dmarc.TXT = [ DMARC ]; - "mail._domainkey".TXT = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8oumqNkHboF/S4dnKue+hEC3V226ToMmL/fmXqbAhsW88m+jUuLgZE8Nl7kc/lzD9yY7JmCXcWFzoLJWE8xusfmT1yMOW9sQmee7g0tHsm1fVqFMUetmC4+QuqAdvjIGU5QndjdWHP/gssIoLPT7lCNUL4/lkaPmFiiDyvaMpkQIDAQAB" ]; - }; - }; - - "zug.network" = { - inherit MX TXT; - - subdomains = { - _dmarc.TXT = [ DMARC ]; - "mail._domainkey".TXT = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCl+VU9Bx/MAxyiYttChdXIlvEMUT5jY4k7n/g5d2ISHBqsH6is8fttOZlBv+N8vEQ1pD+DKNdFKqb2ZXfXPwln4rL5FbzN6O0BRXuATxRpI5WA1Fi3N+us17I8b0YaGK+Se5eQKcqpxZ6x8Ao2f7cRCW7xa6aqGtyDwexsmuw+4QIDAQAB" ]; - }; - }; - }; - - services.nginx = { - enable = true; - virtualHosts."${config.networking.fqdn}" = { - enableACME = true; - forceSSL = true; - }; - }; - - mailserver = { - enable = true; - fqdn = config.networking.fqdn; - - openFirewall = true; - localDnsResolver = false; - virusScanning = false; - - certificateScheme = 1; - certificateFile = "${config.security.acme.certs.${config.networking.fqdn}.directory}/fullchain.pem"; - keyFile = "${config.security.acme.certs.${config.networking.fqdn}.directory}/key.pem"; - - enableManageSieve = true; - enableSubmission = true; - enableSubmissionSsl = true; - enableImap = true; - enableImapSsl = true; - enablePop3 = false; - enablePop3Ssl = false; - - sieveDirectory = "/var/lib/sieve"; - - domains = [ - "ctu.cx" - "ctucx.de" - "thein.ovh" - "zug.network" - ]; - - forwards = { - "mail@zug.network" = "isabelle.kleinheuer@gmail.com"; - }; - - loginAccounts = { - "leah@ctu.cx" = { - hashedPasswordFile = config.age.secrets.mail-password-leah.path; - aliases = [ - "@ctu.cx" - "@ctucx.de" - "leah@thein.ovh" - "leon@thein.ovh" - ]; - }; - - "mail@zug.network" = { - hashedPasswordFile = config.age.secrets.mail-password-zugnetwork.path; - }; - }; - }; - - restic-backups.mail = { - passwordFile = config.age.secrets.restic-mail.path; - paths = [ - "/var/vmail" - "/var/lib/sieve" - ]; - }; - - networking.firewall.allowedTCPPorts = [ 80 443 ]; - -}
diff --git a/machines/trabbi/configuration.nix b/machines/trabbi/configuration.nix @@ -19,6 +19,7 @@ # communication ./matrix-synapse.nix ./pleroma + ./mail.nix # websites ./websites
diff --git a/machines/trabbi/maddy.nix b/machines/trabbi/maddy.nix @@ -0,0 +1,359 @@ +{ inputs, config, lib, pkgs, ... }: + +let + mailboxFilterScript = pkgs.writePythonScriptBin "mailbox-filter.py" (ps: [ ps.toml ps.mail-parser ]) '' + from email.header import Header, decode_header, make_header + import sys, re + import toml, mailparser + + def filter_mail(config, sender, recipient, subject): + for type in [ 'recipient', 'subject', 'sender' ]: + if type not in config: + continue + + for key, value in config[type].items(): + if(re.search("^" + key + "$", str(eval(type)))): + print(value.replace(",", "\n")) + sys.exit(0) + + try: + account_name = sys.argv[1] + config = toml.load('/etc/maddy/filters/mailbox/' + account_name + '.toml') + + if len(sys.argv) > 2: + filter_mail(config, sys.argv[2], sys.argv[3], make_header(decode_header(sys.argv[4]))) + else: + sender = subject = "" + message = mailparser.parse_from_string(sys.stdin.read()) + + if len(message.from_) > 0: + if len(message.from_[0]) == 2: + sender = message.from_[0][1] + + if message.subject is not None: + subject = message.subject + + for recipient in message.to: + filter_mail(config, sender, recipient[1], subject) + + except: + pass + + sys.exit(0) + ''; + + receiveFilterScript = pkgs.writePythonScriptBin "receive-filter.py" (ps: [ ps.toml ]) '' + import sys, toml + + try: + sender = sys.argv[1] + recipient = sys.argv[2] + config = toml.load('/etc/maddy/filters/receive.toml') + + for type in [ 'recipient', 'sender' ]: + if type not in config: + continue + + if 'reject' in config[type]: + if eval(type) in config[type]['reject']: + sys.exit(10) + + if 'quarantine' in config[type]: + if eval(type) in config[type]['quarantine']: + sys.exit(20) + + except SystemExit as e: + sys.exit(e) + + except: + pass + + sys.exit(0) + ''; + +in { + + environment.etc."maddy/filters/mailbox/leah@ctu.cx.toml".text = "${inputs.nix-std.lib.serde.toTOML inputs.local-secrets.maddy.mailboxFilter}"; + environment.etc."maddy/filters/receive.toml".text = "${inputs.nix-std.lib.serde.toTOML inputs.local-secrets.maddy.receiveFilter}"; + + security.acme.certs."${config.networking.fqdn}".reloadServices = [ "maddy.service" ]; + + systemd.services.maddy.serviceConfig.ReadOnlyPaths = [ "/etc/maddy/filters" ]; + systemd.services.maddy-restarter = { + script = "${pkgs.systemd}/bin/systemctl restart maddy.service"; + startAt = "0/12:00:00"; + }; + + age.secrets.restic-maddy.file = ../../secrets/trabbi/restic/maddy.age; + + networking.firewall.allowedTCPPorts = [ 25 143 465 587 993 ]; + + dns.zones = with pkgs.dns.lib.combinators; let + TXT = [ "v=spf1 a mx ip4:185.232.70.80 +ip6:2a03:4000:4e:af1::1 ~all" ]; + DMARC = "v=DMARC1; p=none"; + MX = with mx; [ (mx 10 "${config.networking.fqdn}.") ]; + in { + "ctu.cx" = { + inherit MX TXT; + + SRV = [ + { proto = "tcp"; service = "imaps"; priority = 0; weight = 1; port = 993; target = "${config.networking.fqdn}."; } + { proto = "tcp"; service = "imap"; priority = 0; weight = 1; port = 143; target = "${config.networking.fqdn}."; } + { proto = "tcp"; service = "submission"; priority = 0; weight = 1; port = 587; target = "${config.networking.fqdn}."; } + ]; + + subdomains = { + _dmarc.TXT = [ DMARC ]; + "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=nWRKCHE19fL1RHJ2cVkC8Xvfzm9OtgeF5VC2lD+EaEo=" ]; + }; + }; + + "ctucx.de" = { + inherit MX TXT; + + subdomains = { + _dmarc.TXT = [ DMARC ]; + "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=U9JMZlv7BpLXGIpO7WdJ/7ephxwJtJ02jaVUUadyP9s" ]; + }; + }; + + "thein.ovh" = { + inherit MX TXT; + + subdomains = { + _dmarc.TXT = [ DMARC ]; + "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=KYkebiXYSc/+7Rtdz/ZZFRAXAsQnyLPYA6r2uboh5oc=" ]; + }; + }; + + "zug.network" = { + inherit MX TXT; + + subdomains = { + _dmarc.TXT = [ DMARC ]; + "default._domainkey".TXT = [ "v=DKIM1; k=ed25519; p=dH1h2nvPlmT0lIrGddpYRZFTm0AD6D+fsU36McEso2g=" ]; + }; + }; + }; + + users.groups.maddy = {}; + users.users.maddy = { + isSystemUser = true; + home = "/var/lib/maddy"; + group = "maddy"; + extraGroups = [ "nginx" ]; + }; + + restic-backups.maddy = { + user = "maddy"; + passwordFile = config.age.secrets.restic-maddy.path; + paths = [ "/var/lib/maddy" ]; + }; + + services.maddy = { + enable = true; + user = "maddy"; + group = "maddy"; + hostname = config.networking.fqdn; + primaryDomain = "ctu.cx"; + localDomains = [ + "$(hostname)" + "$(primary_domain)" + "thein.ovh" + "ctucx.de" + "trans-agenda.de" + "zug.network" + ]; + + config = '' + #debug on + log syslog + + tls file /var/lib/acme/${config.networking.fqdn}/fullchain.pem /var/lib/acme/${config.networking.fqdn}/key.pem + + # ---------------------------------------------------------------------------- + # Local storage & authentication + + # pass_table provides local hashed passwords storage for authentication of + # users. It can be configured to use any "table" module, in default + # configuration a table in SQLite DB is used. + # Table can be replaced to use e.g. a file for passwords. Or pass_table module + # can be replaced altogether to use some external source of credentials (e.g. + # PAM, /etc/shadow file). + # + # If table module supports it (sql_table does) - credentials can be managed + # using 'maddyctl creds' command. + + auth.pass_table local_authdb { + table sql_table { + driver sqlite3 + dsn credentials.db + table_name passwords + } + } + + # imapsql module stores all indexes and metadata necessary for IMAP using a + # relational database. It is used by IMAP endpoint for mailbox access and + # also by SMTP & Submission endpoints for delivery of local messages. + # + # IMAP accounts, mailboxes and all message metadata can be inspected using + # imap-* subcommands of maddyctl utility. + + storage.imapsql local_mailboxes { + driver sqlite3 + dsn imapsql.db + disable_recent false + compression zstd + + imap_filter { + command ${mailboxFilterScript} {account_name} + } + } + + # ---------------------------------------------------------------------------- + # SMTP endpoints + message routing + + msgpipeline local_routing { + dmarc yes + check { + require_mx_record + dkim + spf { + permerr_action ignore + } + command ${receiveFilterScript} {sender} {rcpts} { + run_on rcpt + + code 1 ignore + code 2 ignore + code 10 reject + code 20 quarantine + } + } + + destination postmaster $(local_domains) { + modify { + replace_rcpt static { + entry postmaster postmaster@$(primary_domain) + entry leon@thein.ovh leah@ctu.cx + entry aufsicht@zug.network mail@zug.network + entry verwaltung@zug.network mail@zug.network + } + + # Implement plus-address notation. + replace_rcpt regexp "(.+)\+(.+)@(.+)" "$1@$3" + + replace_rcpt regexp "(.+)@ctucx.de" "leah@ctu.cx" + replace_rcpt regexp "(.+)@ctu.cx" "leah@ctu.cx" + } + + deliver_to &local_mailboxes + } + + default_destination { + reject 550 5.1.1 "User doesn't exist" + } + } + + smtp tcp://0.0.0.0:25 { + limits { + # Up to 20 msgs/sec across max. 10 SMTP connections. + all rate 20 1s + all concurrency 10 + } + + source $(local_domains) { + reject 501 5.1.8 "Use Submission for outgoing SMTP" + } + + default_source { + destination postmaster $(local_domains) { + deliver_to &local_routing + } + + default_destination { + reject 550 5.1.1 "User doesn't exist" + } + } + } + + submission tls://0.0.0.0:465 tcp://0.0.0.0:587 { + limits { + # Up to 50 msgs/sec across any amount of SMTP connections. + all rate 50 1s + } + + auth &local_authdb + + source $(local_domains) { + destination postmaster $(local_domains) { + deliver_to &local_routing + } + + default_destination { + modify { + dkim $(primary_domain) $(local_domains) default { + newkey_algo ed25519 + } + } + deliver_to &remote_queue + } + } + + default_source { + reject 501 5.1.8 "Non-local sender domain" + } + } + + target.remote outbound_delivery { + limits { + # Up to 20 msgs/sec across max. 10 SMTP connections + # for each recipient domain. + destination rate 20 1s + destination concurrency 10 + } + + mx_auth { + dane + + mtasts { + cache fs + fs_dir mtasts_cache/ + } + + local_policy { + min_tls_level encrypted + min_mx_level none + } + } + } + + target.queue remote_queue { + target &outbound_delivery + max_parallelism 16 + max_tries 4 + + autogenerated_msg_domain $(primary_domain) + + bounce { + destination postmaster $(local_domains) { + deliver_to &local_routing + } + + default_destination { + reject 550 5.0.0 "Refusing to send DSNs to non-local addresses" + } + } + } + + # ---------------------------------------------------------------------------- + # IMAP endpoints + + imap tls://0.0.0.0:993 tcp://0.0.0.0:143 { + auth &local_authdb + storage &local_mailboxes + } + ''; + }; + +}
diff --git a/machines/trabbi/mail.nix b/machines/trabbi/mail.nix @@ -0,0 +1,158 @@ +{ inputs, pkgs, config, ... }: + +{ + + imports = [ + inputs.simple-nixos-mailserver.nixosModule + ]; + + age.secrets.restic-mail.file = ../../secrets/trabbi/restic/mail.age; + age.secrets.mail-password-leah.file = ../../secrets/trabbi/mail/password-leah-ctu.cx.age; + age.secrets.mail-password-f2k1de.file = ../../secrets/trabbi/mail/password-hi-f2k1.de.age; + age.secrets.mail-password-zugnetwork.file = ../../secrets/trabbi/mail/password-mail-zug.network.age; + + dns.zones = with pkgs.dns.lib.combinators; let + TXT = [ "v=spf1 a mx ip4:89.58.62.171 +ip6:2a0a:4cc0:1:2d7::1 ~all" ]; + DMARC = "v=DMARC1; p=none"; + MX = with mx; [ (mx 10 "${config.networking.fqdn}.") ]; + in { + "ctu.cx" = { + inherit MX TXT; + + SRV = [ + { proto = "tcp"; service = "imaps"; priority = 0; weight = 1; port = 993; target = "${config.networking.fqdn}."; } + { proto = "tcp"; service = "imap"; priority = 0; weight = 1; port = 143; target = "${config.networking.fqdn}."; } + { proto = "tcp"; service = "submission"; priority = 0; weight = 1; port = 587; target = "${config.networking.fqdn}."; } + ]; + + subdomains = { + _dmarc.TXT = [ DMARC ]; + "mail._domainkey".TXT = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDKryfX99NkcU5Xe4AmG+kO/sfuYSXk5RqJhzxS4uMqERE8UszgEGdteXcD8pqON2MfDmA3G6cA+Oa+N4tIWdIYNwTISVXXMGdHvjFIsVUEW0turM104tXESELaPRntkCvDBk/yOgsBDRZQHSx5MdGwpzeRC8TLdCbalh3W0jp5PQIDAQAB" ]; + }; + }; + + "ctucx.de" = { + inherit MX TXT; + + subdomains = { + _dmarc.TXT = [ DMARC ]; + "mail._domainkey".TXT = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5fu690bKYCZLPAFfQQK+nl+aAmtetaWBKCWzGj6pt7HjpFjystgtgnQ6+DZLFXWUp8GRfMEycySB5kQULtYtSMUmx0gQBnTTLsRj+e55/CYUllLV6YXb5uca7LuVhlWPpH3sCr6TvC2VFWe4t0UC3uIXhYPrCm6p8OE7g+TdHHwIDAQAB" ]; + }; + }; + + "thein.ovh" = { + inherit MX TXT; + + subdomains = { + _dmarc.TXT = [ DMARC ]; + "mail._domainkey".TXT = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8oumqNkHboF/S4dnKue+hEC3V226ToMmL/fmXqbAhsW88m+jUuLgZE8Nl7kc/lzD9yY7JmCXcWFzoLJWE8xusfmT1yMOW9sQmee7g0tHsm1fVqFMUetmC4+QuqAdvjIGU5QndjdWHP/gssIoLPT7lCNUL4/lkaPmFiiDyvaMpkQIDAQAB" ]; + }; + }; + + "zug.network" = { + inherit MX TXT; + + subdomains = { + _dmarc.TXT = [ DMARC ]; + "mail._domainkey".TXT = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCl+VU9Bx/MAxyiYttChdXIlvEMUT5jY4k7n/g5d2ISHBqsH6is8fttOZlBv+N8vEQ1pD+DKNdFKqb2ZXfXPwln4rL5FbzN6O0BRXuATxRpI5WA1Fi3N+us17I8b0YaGK+Se5eQKcqpxZ6x8Ao2f7cRCW7xa6aqGtyDwexsmuw+4QIDAQAB" ]; + }; + }; + + "flauschehorn.sexy" = { + inherit MX TXT; + + subdomains = { + _dmarc.TXT = [ DMARC ]; + "mail._domainkey".TXT = [ "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvEPR8068KtlsiWiexSPWqagKmd07ggGvDcYICzOvhxVB0MDrn+/VYIXEbVX0Y9z60oT1ynjkhFjDWEofk11EoXwrg7xjkqZuszDrhdYqUnoLrzlugmnK4jXO3cAD0qeblX0rDmu30cmPP1Aj21tLTU6loYpORY+y4VaVfwtHswwIDAQAB" ]; + }; + }; + }; + + services.nginx = { + enable = true; + virtualHosts."${config.networking.fqdn}" = { + enableACME = true; + forceSSL = true; + }; + }; + + mailserver = { + enable = true; + fqdn = config.networking.fqdn; + + openFirewall = true; + localDnsResolver = false; + virusScanning = false; + + certificateScheme = 1; + certificateFile = "${config.security.acme.certs.${config.networking.fqdn}.directory}/fullchain.pem"; + keyFile = "${config.security.acme.certs.${config.networking.fqdn}.directory}/key.pem"; + + enableManageSieve = true; + enableSubmission = true; + enableSubmissionSsl = true; + enableImap = true; + enableImapSsl = true; + enablePop3 = false; + enablePop3Ssl = false; + + sieveDirectory = "/var/lib/sieve"; + + domains = [ + "ctu.cx" + "ctucx.de" + "thein.ovh" + "zug.network" + "flauschehorn.sexy" + "f2k1.de" + ]; + + forwards = let + f2k1deGMail = "isabelle.kleinheuer@gmail.com"; + in { + "mail@zug.network" = f2k1deGMail; + "@f2k1.de" = f2k1deGMail; + "luna@flauschehorn.sexy" = f2k1deGMail; + "jules@flauschehorn.sexy" = f2k1deGMail; + }; + + loginAccounts = { + "leah@ctu.cx" = { + hashedPasswordFile = config.age.secrets.mail-password-leah.path; + aliases = [ + "@ctu.cx" + "@ctucx.de" + "leah@thein.ovh" + "leon@thein.ovh" + ]; + }; + + "hi@f2k1.de" = { + hashedPasswordFile = config.age.secrets.mail-password-f2k1de.path; + aliases = [ + "@f2k1.de" + "luna@flauschehorn.sexy" + "jules@flauschehorn.sexy" + ]; + }; + + "mail@zug.network" = { + hashedPasswordFile = config.age.secrets.mail-password-zugnetwork.path; + aliases = [ + "@zug.network" + ]; + }; + }; + }; + + restic-backups.mail = { + passwordFile = config.age.secrets.restic-mail.path; + paths = [ + "/var/vmail" + "/var/lib/sieve" + ]; + }; + + networking.firewall.allowedTCPPorts = [ 80 443 ]; + +}
diff --git a/secrets/osterei/mail/password-leah-ctu.cx.age b/secrets/osterei/mail/password-leah-ctu.cx.age @@ -1,10 +0,0 @@ -age-encryption.org/v1 --> X25519 FO3OdOxS0r3BjdAbaW6bG9/PY2XnpMUJFykZ4EwRnEU -52gk0WMZ7kWiLNWe8cc5D05xmwIY3r8H6M7+fBxv7V0 --> ssh-ed25519 YtLkIw CDq8sMvxkTQw1mL+LSit0qzM4mfcXiAByFwIFjY2YA4 -tW2j6Ndh+oMeKXeVVT6BMj52xI1qT+/lBph+yKFrScA --> ':6:46c-grease 4tr\-3+( ?XW8 -Dk1wNczSfkEnyo+/ZkoS6Q13+rqc+4toHisQJio ---- cMuExgPkB0lVkq1NrV1e7TVEX8R8Fm0HJspvC9UqhJc -e ->q4Nzѹ`;O|ZDU}fZ2`G#{ `D=沙*(X>+}ww- \ No newline at end of file
diff --git a/secrets/osterei/mail/password-mail-zug.network.age b/secrets/osterei/mail/password-mail-zug.network.age @@ -1,10 +0,0 @@ -age-encryption.org/v1 --> X25519 jM6TcdXhxBQwwuCFoRbluBB3zpKF3eKcACCNzGogcgk -B5i51+0LZET7TMhCLV9ieBbmAplobRZo7rroXwGfnFc --> ssh-ed25519 YtLkIw OUm9LIlbU2K5uh1EB2mT03qj8SA6XV0P9SuvKD3rsVg -cXsO9X06aOkJvEEhivG/3VrBRnuOu6hcPiKZ+0prGgQ --> N-grease 83( Dzlt %Y%P4Nfr @x\9Q` -dicuHfdWYbh6DWWbZ4jKdEIRj18phFbhlJF5IhvwIMRy2Dtc0qTFrT0Y2+ApADKr -bw3dsKu9BDbvVbMBgU/DPE5EpgGQkG9Zd+roZvfUCxAVncZx7AEDqQyvDJdq ---- HHF5yKveFP0SkQhMnTtioGLsZH3OnqobqSFNGxGBUXE -0q?rILtw)CJO ;w~=g1h,|c<=;~2+-1״JQ|$< O7]0j- \ No newline at end of file
diff --git a/secrets/osterei/restic/maddy.age b/secrets/osterei/restic/maddy.age Binary files differ.
diff --git a/secrets/osterei/restic/mail.age b/secrets/osterei/restic/mail.age @@ -1,10 +0,0 @@ -age-encryption.org/v1 --> X25519 cCSDbJ7zu5HtOlzpRywyHXgwA4eiSU8xNe03E8PZGA8 -BIgMg2l0Q2Nu33DSQWjwNCG7z3yjzanykRnetTpKEQA --> ssh-ed25519 YtLkIw zxiLHeUfKpYtJ87W0zTKpLVqPNDDkU0WxZa5HY42eDo -SZ0zRqtMlPoUTavjV1L22chFLpua4B9g0JcU0Z3Rdc8 --> 3q_#-grease 7{nBL L:c($" LqM1l -Wj8i8sG2iXUyzZQ1+LT2bQNTRKljscxo7Vezs6zV5XnYBc8zqffzg2erM1bQ ---- 32iEWmy2KzWLbiBKYJgbVKCovbQTi4xEkPWoZE6Qp9c -w =3 -.Tz8!yx>r\ONov;'_QfFOޜl>- \ No newline at end of file
diff --git a/secrets/secrets.nix b/secrets/secrets.nix @@ -58,13 +58,6 @@ in { "desastro/restic/syncthing-wiki.age".publicKeys = [ leah desastro ]; - "osterei/mail/password-leah-ctu.cx.age".publicKeys = [ leah osterei ]; - "osterei/mail/password-mail-zug.network.age".publicKeys = [ leah osterei ]; - - "osterei/restic/maddy.age".publicKeys = [ leah osterei ]; - "osterei/restic/mail.age".publicKeys = [ leah osterei ]; - - "taurus/syncthing/key.age".publicKeys = [ leah taurus ]; "taurus/syncthing/cert.age".publicKeys = [ leah taurus ]; @@ -78,5 +71,10 @@ in { "trabbi/restic/gitolite.age".publicKeys = [ leah trabbi ]; "trabbi/restic/pleroma.age".publicKeys = [ leah trabbi ]; "trabbi/restic/matrix-synapse.age".publicKeys = [ leah trabbi ]; + "trabbi/restic/mail.age".publicKeys = [ leah trabbi ]; + + "trabbi/mail/password-leah-ctu.cx.age".publicKeys = [ leah trabbi ]; + "trabbi/mail/password-mail-zug.network.age".publicKeys = [ leah trabbi ]; + "trabbi/mail/password-hi-f2k1.de.age".publicKeys = [ leah trabbi ]; }
diff --git a/secrets/trabbi/mail/password-hi-f2k1.de.age b/secrets/trabbi/mail/password-hi-f2k1.de.age @@ -0,0 +1,11 @@ +age-encryption.org/v1 +-> X25519 XZyoo4FCVLWBeQ00RbAle7isGO1fgbtIF/qnPKVJ7j4 +546fJ+wXlF9W7U/VBZ7urlCUrdHN10ioS2u6+g4G5JI +-> ssh-ed25519 V0uUrw qNC3uzdKvLFzB9A1qjcQzuoCHzhUaGnSTAZ/60e4jDc +xDWaORFCcNd+B5mQRPTmUQiHCO4D/Qf9WgSO6noBz+k +-> '%+UVl-grease +dSJbcG6mfL/F6S1Fzkp2YjVTnz5p95E95jAcxptL6SHASxDav5PRnEEVpFD7fe0D +NosjPkVNJU1fvNT0HYy1JXE4na4GonqWgJt+qkfVJrs +--- iPqoX9v3g8XC3WJApj291Ap7pYht9JPihfV0E00cMcw +V{>z"ZP^&1O<7-ېT|3Ih*xzyX)^ݥ +Z,@kP!ꆒ;SW%Z+ \ No newline at end of file
diff --git a/secrets/trabbi/mail/password-leah-ctu.cx.age b/secrets/trabbi/mail/password-leah-ctu.cx.age @@ -0,0 +1,10 @@ +age-encryption.org/v1 +-> X25519 J3xl715kbassDnB/897gOqLocKxih1vyjzNCTiRrpk8 +orFBsIZ4tFcsei1fMM8aPqt3CDU4CFEL4T/Gh0ucweQ +-> ssh-ed25519 V0uUrw QO8ZMvxHBKBC9/8+ZtHPiAego1rNsaKGJdbw2y/lqAg +snRBumNgdh8PctXtUd8ULIH8pSSQJO3Agm3+LBzHRU4 +-> K$rfa3-grease +9mGxzw+MqSpztgSwDx8KlA8 +--- IR3n4OUmXMCO7GE8f5Ude9pO9/6d3+40q1edasUYXCA +{=!\[h}?b"NM<LjIJLg +fCmK8Lj' ,1?h5+ \ No newline at end of file
diff --git a/secrets/trabbi/mail/password-mail-zug.network.age b/secrets/trabbi/mail/password-mail-zug.network.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> X25519 R5XejExMhGQ682LEZgyraz5miW7WWmsArHEcOmdrvnQ +DQ7BBzkumyhIU1rBptLolj2+MaRoKqBDXdBCtwCtees +-> ssh-ed25519 V0uUrw 9MoCNlpVZJwiQCDT8SFfnSB2WcfozOzQcU2cno6Z5QI +K5jWpjTrlED/uMx82K99oeJqbF4+uOZ62m7/q+iRIrM +-> bB-U$gk-grease *l] t< {>wnp ^4XjF +5s6vCwc5ZVv5YrpbYwnb +--- vW/3ki8vaPkGmcnjU101qJNF3+wfQkUj7wYO5zsxLzE +sdnǟ<4R[,ޓ~{j$z}l@iPvkWė \&{LB=>lpdK95+ \ No newline at end of file
diff --git a/secrets/trabbi/restic/mail.age b/secrets/trabbi/restic/mail.age Binary files differ.