1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7
8let
9 inherit (lib)
10 hasPrefix
11 literalExpression
12 mkEnableOption
13 mkIf
14 mkMerge
15 mkOption
16 mkPackageOption
17 types
18 ;
19
20 cfg = config.services.postfix-tlspol;
21
22 format = pkgs.formats.yaml_1_2 { };
23 configFile = format.generate "postfix-tlspol.yaml" cfg.settings;
24in
25
26{
27 meta.maintainers = pkgs.postfix-tlspol.meta.maintainers;
28
29 options.services.postfix-tlspol = {
30 enable = mkEnableOption "postfix-tlspol";
31
32 package = mkPackageOption pkgs "postfix-tlspol" { };
33
34 settings = mkOption {
35 type = types.submodule {
36 freeformType = format.type;
37 options = {
38 server = {
39 address = mkOption {
40 type = types.str;
41 default = "unix:/run/postfix-tlspol/tlspol.sock";
42 example = "127.0.0.1:8642";
43 description = ''
44 Path or address/port where postfix-tlspol binds its socket to.
45 '';
46 };
47
48 socket-permissions = mkOption {
49 type = types.str;
50 default = "0660";
51 readOnly = true;
52 description = ''
53 Permissions to the UNIX socket, if configured.
54
55 ::: {.note}
56 Due to hardening on the systemd unit the socket can never be created world readable/writable.
57 :::
58 '';
59 apply = value: (builtins.fromTOML "v=0o${value}").v;
60 };
61
62 log-level = mkOption {
63 type = types.enum [
64 "debug"
65 "info"
66 "warn"
67 "error"
68 ];
69 default = "info";
70 example = "warn";
71 description = ''
72 Log level
73 '';
74 };
75
76 prefetch = mkOption {
77 type = types.bool;
78 default = true;
79 example = false;
80 description = ''
81 Whether to prefetch DNS records when the TTL of a cached record is about to expire.
82 '';
83 };
84
85 cache-file = mkOption {
86 type = types.path;
87 default = "/var/cache/postfix-tlspol/cache.db";
88 readOnly = true;
89 description = ''
90 Path to the cache file.
91 '';
92 };
93 };
94
95 dns = {
96 address = mkOption {
97 type = with types; nullOr str;
98 default = null;
99 example = "127.0.0.1:53";
100 description = ''
101 IP and port to your DNS resolver.
102
103 Uses resolvers from /etc/resolv.conf if unset.
104
105 ::: {.note}
106 The configured DNS resolver must validate DNSSEC signatures.
107 :::
108 '';
109 };
110 };
111 };
112 };
113
114 default = { };
115 description = ''
116 The postfix-tlspol configuration file as a Nix attribute set.
117
118 See the reference documentation for possible options.
119 <https://github.com/Zuplu/postfix-tlspol/blob/main/configs/config.default.yaml>
120 '';
121 };
122
123 configurePostfix = mkOption {
124 type = types.bool;
125 default = true;
126 description = ''
127 Whether to configure the required settings to use postfix-tlspol in the local Postfix instance.
128 '';
129 };
130 };
131
132 config = mkMerge [
133 (mkIf (cfg.enable && config.services.postfix.enable && cfg.configurePostfix) {
134 # https://github.com/Zuplu/postfix-tlspol#postfix-configuration
135 services.postfix.settings.main = {
136 smtp_dns_support_level = "dnssec";
137 smtp_tls_security_level = "dane";
138 smtp_tls_policy_maps =
139 let
140 address =
141 if (hasPrefix "unix:" cfg.settings.server.address) then
142 cfg.settings.server.address
143 else
144 "inet:${cfg.settings.server.address}";
145 in
146 [ "socketmap:${address}:QUERYwithTLSRPT" ];
147 };
148
149 systemd.services.postfix = {
150 wants = [ "postfix-tlspol.service" ];
151 after = [ "postfix-tlspol.service" ];
152 };
153
154 users.users.postfix.extraGroups = [ "postfix-tlspol" ];
155 })
156
157 (mkIf cfg.enable {
158 environment.etc."postfix-tlspol/config.yaml".source = configFile;
159
160 environment.systemPackages = [ cfg.package ];
161
162 users.users.postfix-tlspol = {
163 isSystemUser = true;
164 group = "postfix-tlspol";
165 };
166 users.groups.postfix-tlspol = { };
167
168 systemd.services.postfix-tlspol = {
169 after = [
170 "nss-lookup.target"
171 "network-online.target"
172 ];
173 wants = [
174 "nss-lookup.target"
175 "network-online.target"
176 ];
177 wantedBy = [ "multi-user.target" ];
178
179 description = "Postfix DANE/MTA-STS TLS policy socketmap service";
180 documentation = [ "https://github.com/Zuplu/postfix-tlspol" ];
181
182 restartTriggers = [ configFile ];
183
184 # https://github.com/Zuplu/postfix-tlspol/blob/main/init/postfix-tlspol.service
185 serviceConfig = {
186 ExecStart = toString [
187 (lib.getExe cfg.package)
188 "-config"
189 "/etc/postfix-tlspol/config.yaml"
190 ];
191 ExecReload = "${lib.getExe' pkgs.util-linux "kill"} -HUP $MAINPID";
192 Restart = "always";
193 RestartSec = 5;
194
195 User = "postfix-tlspol";
196 Group = "postfix-tlspol";
197
198 CacheDirectory = "postfix-tlspol";
199 CapabilityBoundingSet = [ "" ];
200 LockPersonality = true;
201 MemoryDenyWriteExecute = true;
202 NoNewPrivileges = true;
203 PrivateDevices = true;
204 PrivateTmp = true;
205 PrivateUsers = true;
206 ProcSubset = "pid";
207 ProtectClock = true;
208 ProtectControlGroups = true;
209 ProtectHome = true;
210 ProtectHostname = true;
211 ProtectKernelLogs = true;
212 ProtectKernelModules = true;
213 ProtectKernelTunables = true;
214 ProtectProc = "invisible";
215 ProtectSystem = "strict";
216 ReadOnlyPaths = [ "/etc/postfix-tlspol/config.yaml" ];
217 RemoveIPC = true;
218 RestrictAddressFamilies = [
219 "AF_INET"
220 "AF_INET6"
221 ]
222 ++ lib.optionals (lib.hasPrefix "unix:" cfg.settings.server.address) [
223 "AF_UNIX"
224 ];
225 RestrictNamespaces = true;
226 RestrictRealtime = true;
227 RestrictSUIDSGID = true;
228 SystemCallArchitectures = "native";
229 SystemCallFilter = [
230 "@system-service"
231 "~@privileged @resources"
232 ];
233 SystemCallErrorNumber = "EPERM";
234 SecureBits = [
235 "noroot"
236 "noroot-locked"
237 ];
238 RuntimeDirectory = "postfix-tlspol";
239 RuntimeDirectoryMode = "1750";
240 WorkingDirectory = "/var/cache/postfix-tlspol";
241 UMask = "0117";
242 };
243 };
244 })
245 ];
246}