From 7c0f92f70bbe9ae6441bd68069970a9e7ace91bd Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Tue, 10 Jun 2025 04:36:12 +0200 Subject: [PATCH] nixos/postfix-tlspol: init MTA-STS and DANE/TLSA resolver and TLS policy socketmap server for Postfix. --- .../manual/release-notes/rl-2511.section.md | 2 + nixos/modules/module-list.nix | 1 + .../modules/services/mail/postfix-tlspol.nix | 220 ++++++++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 nixos/modules/services/mail/postfix-tlspol.nix diff --git a/nixos/doc/manual/release-notes/rl-2511.section.md b/nixos/doc/manual/release-notes/rl-2511.section.md index 932ff7b6d6f7..79607b94c59a 100644 --- a/nixos/doc/manual/release-notes/rl-2511.section.md +++ b/nixos/doc/manual/release-notes/rl-2511.section.md @@ -26,6 +26,8 @@ - [Draupnir](https://github.com/the-draupnir-project/draupnir), a Matrix moderation bot. Available as [services.draupnir](#opt-services.draupnir.enable). +- [postfix-tlspol](https://github.com/Zuplu/postfix-tlspol), MTA-STS and DANE resolver and TLS policy server for Postfix. Available as [services.postfix-tlspol](#opt-services.postfix-tlspol.enable). + - [SuiteNumérique Docs](https://github.com/suitenumerique/docs), a collaborative note taking, wiki and documentation web platform and alternative to Notion or Outline. Available as [services.lasuite-docs](#opt-services.lasuite-docs.enable). [dwl](https://codeberg.org/dwl/dwl), a compact, hackable compositor for Wayland based on wlroots. Available as [programs.dwl](#opt-programs.dwl.enable). diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 77055c886318..7c2e1205169f 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -739,6 +739,7 @@ ./services/mail/opendkim.nix ./services/mail/opensmtpd.nix ./services/mail/pfix-srsd.nix + ./services/mail/postfix-tlspol.nix ./services/mail/postfix.nix ./services/mail/postfixadmin.nix ./services/mail/postgrey.nix diff --git a/nixos/modules/services/mail/postfix-tlspol.nix b/nixos/modules/services/mail/postfix-tlspol.nix new file mode 100644 index 000000000000..01373659da4a --- /dev/null +++ b/nixos/modules/services/mail/postfix-tlspol.nix @@ -0,0 +1,220 @@ +{ + config, + lib, + pkgs, + ... +}: + +let + inherit (lib) + hasPrefix + mkEnableOption + mkIf + mkOption + mkPackageOption + types + ; + + cfg = config.services.postfix-tlspol; + + format = pkgs.formats.yaml_1_2 { }; +in + +{ + options.services.postfix-tlspol = { + enable = mkEnableOption "postfix-tlspol"; + + package = mkPackageOption pkgs "postfix-tlspol" { }; + + settings = mkOption { + type = types.submodule { + freeformType = format.type; + options = { + server = { + address = mkOption { + type = types.str; + default = "unix:/run/postfix-tlspol/tlspol.sock"; + example = "127.0.0.1:8642"; + description = '' + Path or address/port where postfix-tlspol binds its socket to. + ''; + }; + + socket-permissions = mkOption { + type = types.str; + default = "0660"; + readOnly = true; + description = '' + Permissions to the UNIX socket, if configured. + + ::: {.note} + Due to hardening on the systemd unit the socket can never be created world readable/writable. + ::: + ''; + apply = value: (builtins.fromTOML "v=0o${value}").v; + }; + + log-level = mkOption { + type = types.enum [ + "debug" + "info" + "warn" + "error" + ]; + default = "info"; + example = "warn"; + description = '' + Log level + ''; + }; + + prefetch = mkOption { + type = types.bool; + default = true; + example = false; + description = '' + Whether to prefetch DNS records when the TTL of a cached record is about to expire. + ''; + }; + + cache-file = mkOption { + type = types.path; + default = "/var/cache/postfix-tlspol/cache.db"; + readOnly = true; + description = '' + Path to the cache file. + ''; + }; + }; + + dns = { + server = mkOption { + type = types.str; + default = "127.0.0.1:53"; + description = '' + IP and port to your DNS resolver + + ::: {.note} + The configured DNS resolver must validate DNSSEC signatures. + ::: + ''; + }; + }; + }; + }; + + default = { }; + description = '' + The postfix-tlspol configuration file as a Nix attribute set. + + See the reference documentation for possible options. + + ''; + }; + + configurePostfix = mkOption { + type = types.bool; + default = true; + description = '' + Whether to configure the required settings to use postfix-tlspol in the local Postfix instance. + ''; + }; + }; + + config = mkIf cfg.enable { + environment.etc."postfix-tlspol/config.yaml".source = + format.generate "postfix-tlspol.yaml" cfg.settings; + + environment.systemPackages = [ cfg.package ]; + + # https://github.com/Zuplu/postfix-tlspol#postfix-configuration + services.postfix.config = mkIf (config.services.postfix.enable && cfg.configurePostfix) { + smtp_dns_support_level = "dnssec"; + smtp_tls_security_level = "dane"; + smtp_tls_policy_maps = + let + address = + if (hasPrefix "unix:" cfg.settings.server.address) then + cfg.settings.server.address + else + "inet:${cfg.settings.server.address}"; + in + [ "socketmap:${address}:QUERYwithTLSRPT" ]; + }; + + systemd.services.postfix-tlspol = { + after = [ + "nss-lookup.target" + "network-online.target" + ]; + wants = [ + "nss-lookup.target" + "network-online.target" + ]; + wantedBy = [ "multi-user.target" ]; + + description = "Postfix DANE/MTA-STS TLS policy socketmap service"; + documentation = [ "https://github.com/Zuplu/postfix-tlspol" ]; + + # https://github.com/Zuplu/postfix-tlspol/blob/main/init/postfix-tlspol.service + serviceConfig = { + ExecStart = toString [ + (lib.getExe cfg.package) + "-config" + "/etc/postfix-tlspol/config.yaml" + ]; + ExecReload = "${lib.getExe' pkgs.util-linux "kill"} -HUP $MAINPID"; + Restart = "always"; + RestartSec = 5; + + DynamicUser = true; + + CacheDirectory = "postfix-tlspol"; + CapabilityBoundingSet = [ "" ]; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProcSubset = "pid"; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "strict"; + ReadOnlyPaths = [ "/etc/postfix-tlspol/config.yaml" ]; + RemoveIPC = true; + RestrictAddressFamilies = + [ + "AF_INET" + "AF_INET6" + ] + ++ lib.optionals (lib.hasPrefix "unix:" cfg.settings.server.address) [ + "AF_UNIX" + ]; + RestrictNamespace = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged @resources" + ]; + SystemCallErrorNumber = "EPERM"; + SecureBits = [ + "noroot" + "noroot-locked" + ]; + RuntimeDirectory = "postfix-tlspol"; + RuntimeDirectoryMode = "1750"; + WorkingDirectory = "/var/cache/postfix-tlspol"; + UMask = "0117"; + }; + }; + }; +}