From 56a3226347e9b9a3bff0db4c5a7e37fac9ebc44b Mon Sep 17 00:00:00 2001 From: ChaotiCryptidz Date: Wed, 22 Jun 2022 16:59:41 +0100 Subject: [PATCH] webmail dkim shenanigan backup --- hosts/hetzner-vm/services/mail.nix | 20 ++--- .../hetzner-vm/services/mailserver/config.nix | 8 +- .../services/mailserver/opendkim.nix | 76 +++++++++++++++++++ .../services/mailserver/postfix.nix | 60 +++++++++------ .../services/mailserver/webmail.nix | 13 ++++ hosts/hetzner-vm/services/restic.nix | 13 +++- 6 files changed, 155 insertions(+), 35 deletions(-) create mode 100644 hosts/hetzner-vm/services/mailserver/opendkim.nix create mode 100644 hosts/hetzner-vm/services/mailserver/webmail.nix diff --git a/hosts/hetzner-vm/services/mail.nix b/hosts/hetzner-vm/services/mail.nix index 58bd6d1..6e330f4 100644 --- a/hosts/hetzner-vm/services/mail.nix +++ b/hosts/hetzner-vm/services/mail.nix @@ -1,9 +1,11 @@ -{...}: { - imports = [ - ./mailserver/postfix.nix - ./mailserver/vmail.nix - ./mailserver/ssl.nix - ./mailserver/dovecot.nix - ./mailserver/firewall.nix - ]; -} \ No newline at end of file +{ ... }: { + imports = [ + ./mailserver/postfix.nix + ./mailserver/vmail.nix + ./mailserver/ssl.nix + ./mailserver/dovecot.nix + ./mailserver/firewall.nix + ./mailserver/webmail.nix + ./mailserver/opendkim.nix + ]; +} diff --git a/hosts/hetzner-vm/services/mailserver/config.nix b/hosts/hetzner-vm/services/mailserver/config.nix index af40379..e07ea0a 100644 --- a/hosts/hetzner-vm/services/mailserver/config.nix +++ b/hosts/hetzner-vm/services/mailserver/config.nix @@ -2,7 +2,7 @@ fqdn = "mail.owo.monster"; domains = [ "owo.monster" - #"kitteh.pw" + "kitteh.pw" ]; debug_mode = true; @@ -22,11 +22,17 @@ passwordFile = "/secrets/chaos-mail-password"; aliases = [ "all@owo.monster" + "chaos@owo.monster" + "kitteh@owo.monster" + "kitteh@kitteh.pw" ]; }; }; sieve_directory = "/var/sieve"; + dkim_directory = "/var/dkim"; + + policyd_config = ""; vmail_config = { user_group_name = "vmail"; diff --git a/hosts/hetzner-vm/services/mailserver/opendkim.nix b/hosts/hetzner-vm/services/mailserver/opendkim.nix new file mode 100644 index 0000000..4856c61 --- /dev/null +++ b/hosts/hetzner-vm/services/mailserver/opendkim.nix @@ -0,0 +1,76 @@ +{ config, lib, pkgs, ... }: +let + mail_config = (import ./config.nix { }); + + dkimUser = config.services.opendkim.user; + dkimGroup = config.services.opendkim.group; + + keyDir = mail_config.dkim_directory; + selector = "mail"; + + domains = mail_config.domains; + + createDomainDkimCert = dom: + let + dkim_key = "${keyDir}/${dom}.${selector}.key"; + dkim_txt = "${keyDir}/${dom}.${selector}.txt"; + in '' + if [ ! -f "${dkim_key}" ] + then + ${pkgs.opendkim}/bin/opendkim-genkey -s "${selector}" \ + -d "${dom}" \ + --bits="1024" \ + --directory="${keyDir}" + mv "${keyDir}/${selector}.private" "${dkim_key}" + mv "${keyDir}/${selector}.txt" "${dkim_txt}" + echo "Generated key for domain ${dom} selector ${selector}" + fi + ''; + + createAllCerts = + lib.concatStringsSep "\n" (map createDomainDkimCert mail_config.domains); + + keyTable = pkgs.writeText "opendkim-KeyTable" (lib.concatStringsSep "\n" + (lib.flip map domains + (dom: "${dom} ${dom}:${selector}:${keyDir}/${dom}.${selector}.key"))); + + signingTable = pkgs.writeText "opendkim-SigningTable" + (lib.concatStringsSep "\n" (lib.flip map domains (dom: "${dom} ${dom}"))); + + dkim = config.services.opendkim; + args = [ "-f" "-l" ] + ++ lib.optionals (dkim.configFile != null) [ "-x" dkim.configFile ]; +in { + services.opendkim = { + enable = true; + selector = selector; + keyPath = keyDir; + domains = "csl:${builtins.concatStringsSep "," domains}"; + configFile = pkgs.writeText "opendkim.conf" ('' + Canonicalization relaxed/relaxed + UMask 0002 + Socket ${dkim.socket} + KeyTable file:${keyTable} + SigningTable file:${signingTable} + '' + (lib.optionalString mail_config.debug_mode '' + Syslog yes + SyslogSuccess yes + LogWhy yes + '')); + }; + + users.users = lib.optionalAttrs (config.services.postfix.user == "postfix") { + postfix.extraGroups = [ "${dkimGroup}" ]; + }; + + systemd.services.opendkim = { + preStart = lib.mkForce createAllCerts; + serviceConfig = { + ExecStart = + lib.mkForce "${pkgs.opendkim}/bin/opendkim ${lib.escapeShellArgs args}"; + PermissionsStartOnly = lib.mkForce false; + }; + }; + systemd.tmpfiles.rules = + [ "d '${keyDir}' - ${dkimUser} ${dkimGroup} - -" ]; +} diff --git a/hosts/hetzner-vm/services/mailserver/postfix.nix b/hosts/hetzner-vm/services/mailserver/postfix.nix index 5e617e3..054179a 100644 --- a/hosts/hetzner-vm/services/mailserver/postfix.nix +++ b/hosts/hetzner-vm/services/mailserver/postfix.nix @@ -15,7 +15,6 @@ let inetSocket = addr: port: "inet:[${toString port}@${addr}]"; unixSocket = sock: "unix:${sock}"; - # Merge several lookup tables. A lookup table is a attribute set where # - the key is an address (user@example.com) or a domain (@example.com) # - the value is a list of addresses @@ -25,37 +24,39 @@ let valiases_postfix = mergeLookupTables (lib.flatten (lib.mapAttrsToList (name: value: let to = name; - in map (from: {"${from}" = to;}) (value.aliases ++ lib.singleton name)) + in map (from: { "${from}" = to; }) (value.aliases ++ lib.singleton name)) mail_config.accounts)); # all_valiases_postfix :: Map String [String] - all_valiases_postfix = mergeLookupTables [valiases_postfix]; + all_valiases_postfix = mergeLookupTables [ valiases_postfix ]; # attrsToLookupTable :: Map String (Either String [ String ]) -> Map String [String] - attrsToLookupTable = aliases: let - lookupTables = lib.mapAttrsToList (from: to: {"${from}" = to;}) aliases; - in mergeLookupTables lookupTables; + attrsToLookupTable = aliases: + let + lookupTables = lib.mapAttrsToList (from: to: { "${from}" = to; }) aliases; + in mergeLookupTables lookupTables; # lookupTableToString :: Map String [String] -> String - lookupTableToString = attrs: let - valueToString = value: lib.concatStringsSep ", " value; - in lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs); + lookupTableToString = attrs: + let valueToString = value: lib.concatStringsSep ", " value; + in lib.concatStringsSep "\n" + (lib.mapAttrsToList (name: value: "${name} ${valueToString value}") attrs); # valiases_file :: Path valiases_file = let - content = lookupTableToString (mergeLookupTables [all_valiases_postfix]); + content = lookupTableToString (mergeLookupTables [ all_valiases_postfix ]); in builtins.toFile "valias" content; # vhosts_file :: Path - vhosts_file = builtins.toFile "vhosts" (lib.concatStringsSep "\n" mail_config.domains); - vaccounts_file = builtins.toFile "vaccounts" (lookupTableToString all_valiases_postfix); - - smtpdMilters = [ - # "unix:/run/opendkim/opendkim.sock" - ]; + vhosts_file = + builtins.toFile "vhosts" (lib.concatStringsSep "\n" mail_config.domains); + vaccounts_file = + builtins.toFile "vaccounts" (lookupTableToString all_valiases_postfix); mappedFile = name: "hash:/var/lib/postfix/conf/${name}"; + policyd-spf = pkgs.writeText "policyd-spf.conf" mail_config.policyd_config; + submissionOptions = { smtpd_tls_security_level = "encrypt"; smtpd_sasl_auth_enable = "yes"; @@ -82,8 +83,7 @@ in { sslKey = mail_config.ssl_config.key; enableSubmission = true; enableSubmissions = true; - #virtual = lookupTableToString - # (mergeLookupTables [ all_valiases_postfix catchAllPostfix forwards ]); + virtual = lookupTableToString (mergeLookupTables [ all_valiases_postfix ]); config = { # Extra Config @@ -112,18 +112,18 @@ in { "reject_unauth_destination" ]; - #policy-spf_time_limit = "3600s"; + policy-spf_time_limit = "3600s"; # reject selected senders #smtpd_sender_restrictions = # [ "check_sender_access ${mappedFile "reject_senders"}" ]; # quota and spf checking - #smtpd_recipient_restrictions = [ + smtpd_recipient_restrictions = [ #"check_recipient_access ${mappedFile "denied_recipients"}" #"check_recipient_access ${mappedFile "reject_recipients"}" - #"check_policy_service unix:private/policy-spf" - #]; + "check_policy_service unix:private/policy-spf" + ]; # TLS settings, inspired by https://github.com/jeaye/nix-files # Submission by mail clients is handled in submissionOptions @@ -163,8 +163,9 @@ in { # Configure a non blocking source of randomness tls_random_source = "dev:/dev/urandom"; - smtpd_milters = smtpdMilters; - #non_smtpd_milters = [ "unix:/run/opendkim/opendkim.sock" ]; + smtpd_milters = [ "unix:/run/opendkim/opendkim.sock" ]; + non_smtpd_milters = [ "unix:/run/opendkim/opendkim.sock" ]; + milter_protocol = "6"; milter_mail_macros = "i {mail_addr} {client_addr} {client_name} {auth_type} {auth_authen} {auth_author} {mail_addr} {mail_host} {mail_mailer}"; @@ -180,6 +181,17 @@ in { # D => Delivered-To, O => X-Original-To, R => Return-Path args = [ "flags=O" ]; }; + "policy-spf" = { + type = "unix"; + privileged = true; + chroot = false; + command = "spawn"; + args = [ + "user=nobody" + "argv=${pkgs.pypolicyd-spf}/bin/policyd-spf" + "${policyd-spf}" + ]; + }; "submission-header-cleanup" = { type = "unix"; private = false; diff --git a/hosts/hetzner-vm/services/mailserver/webmail.nix b/hosts/hetzner-vm/services/mailserver/webmail.nix new file mode 100644 index 0000000..65eba3e --- /dev/null +++ b/hosts/hetzner-vm/services/mailserver/webmail.nix @@ -0,0 +1,13 @@ +{ ... }: +let mail_config = (import ./config.nix { }); +in { + services.roundcube = { + enable = true; + hostName = "mail.owo.monster"; + extraConfig = '' + $config['smtp_server'] = "tls://${mail_config.fqdn}"; + $config['smtp_user'] = "%u"; + $config['smtp_pass'] = "%p"; + ''; + }; +} diff --git a/hosts/hetzner-vm/services/restic.nix b/hosts/hetzner-vm/services/restic.nix index b1d9f61..bd78706 100644 --- a/hosts/hetzner-vm/services/restic.nix +++ b/hosts/hetzner-vm/services/restic.nix @@ -1,7 +1,18 @@ _: let + + mail_config = (import ./mailserver/config.nix { }); + backupUser = "root"; - backupPaths = [ "/var/lib/postgresql" "/var/lib/vault" "/var/lib/acme" ]; + backupPaths = [ + "/var/lib/postgresql" + "/var/lib/vault" + "/var/lib/acme" + "/secrets" + mail_config.vmail_config.directory + mail_config.sieve_directory + mail_config.dkim_directory + ]; timerConfig = { OnBootSec = "1m"; OnCalendar = "daily";