at master 11 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 utils, 6 ... 7}: 8let 9 10 cfg = config.services.postsrsd; 11 12 inherit (lib) 13 concatMapStringsSep 14 concatMapAttrsStringSep 15 isBool 16 isFloat 17 isInt 18 isPath 19 isString 20 isList 21 mkEnableOption 22 mkPackageOption 23 mkRemovedOptionModule 24 mkRenamedOptionModule 25 ; 26 27 # This is a implementation of a simple libconfuse config renderer sufficient 28 # for the postsrsd configuration file complexity. 29 # TODO: Replace with pkgs.formats.libconfuse, once implemented (https://github.com/NixOS/nixpkgs/issues/401565) 30 renderValue = 31 value: 32 if isBool value then 33 if value then "true" else "false" 34 else if isString value || isPath value then 35 builtins.toJSON value # for escaping 36 else if isInt value || isFloat value then 37 toString value 38 else if isList value then 39 "{${concatMapStringsSep "," renderValue value}}" 40 else 41 throw "postsrsd: unsupported value type in settings option"; 42 43 renderAttr = 44 attrs: concatMapAttrsStringSep "\n" (name: value: "${name} = ${renderValue value}") attrs; 45 46 configFile = pkgs.writeText "postsrsd.conf" ( 47 renderAttr (lib.filterAttrsRecursive (_: v: v != null) cfg.settings) 48 ); 49in 50{ 51 imports = [ 52 (mkRemovedOptionModule [ "services" "postsrsd" "socketPath" ] '' 53 Configure/reference `services.postsrsd.settings.socketmap` instead. Note that its now required to start with the `inet:` or `unix:` prefix. 54 '') 55 (mkRenamedOptionModule 56 [ "services" "postsrsd" "domains" ] 57 [ "services" "postsrsd" "settings" "domains" ] 58 ) 59 (mkRenamedOptionModule 60 [ "services" "postsrsd" "separator" ] 61 [ "services" "postsrsd" "settings" "separator" ] 62 ) 63 ] 64 ++ 65 map 66 ( 67 name: 68 lib.mkRemovedOptionModule [ "services" "postsrsd" name ] '' 69 `postsrsd` was upgraded to `>= 2.0.0`, with some different behaviors and configuration settings: 70 - NixOS Release Notes: https://nixos.org/manual/nixos/unstable/release-notes#sec-nixpkgs-release-25.05-incompatibilities 71 - NixOS Options Reference: https://nixos.org/manual/nixos/unstable/options#opt-services.postsrsd.enable 72 - Migration instructions: https://github.com/roehling/postsrsd/blob/2.0.10/README.rst#migrating-from-version-1x 73 - Postfix Setup: https://github.com/roehling/postsrsd/blob/2.0.10/README.rst#postfix-setup 74 '' 75 ) 76 [ 77 "domain" 78 "forwardPort" 79 "reversePort" 80 "timeout" 81 "excludeDomains" 82 ]; 83 84 options = { 85 services.postsrsd = { 86 enable = mkEnableOption "the postsrsd SRS server for Postfix."; 87 88 package = mkPackageOption pkgs "postsrsd" { }; 89 90 secretsFile = lib.mkOption { 91 type = lib.types.path; 92 default = "/var/lib/postsrsd/postsrsd.secret"; 93 description = '' 94 Secret keys used for signing and verification. 95 96 ::: {.note} 97 The secret will be generated, if it does not exist at the given path. 98 ::: 99 ''; 100 }; 101 102 settings = lib.mkOption { 103 type = lib.types.submodule { 104 freeformType = 105 with lib.types; 106 attrsOf (oneOf [ 107 bool 108 float 109 int 110 path 111 str 112 (listOf str) 113 ]); 114 115 options = { 116 domains = lib.mkOption { 117 type = with lib.types; listOf str; 118 default = [ ]; 119 example = [ "example.com" ]; 120 description = '' 121 List of local domains, that do not require rewriting. 122 ''; 123 }; 124 125 secrets-file = lib.mkOption { 126 type = lib.types.str; 127 default = "\${CREDENTIALS_DIRECTORY}/secrets-file"; 128 readOnly = true; 129 description = '' 130 Path to the file containing the secret keys. 131 132 ::: {.note} 133 Secrets are passed using `LoadCredential=` on the systemd unit, 134 so this options is read-only. 135 136 Configure {option}`services.postsrsd.secretsFile` instead. 137 ''; 138 }; 139 140 separator = lib.mkOption { 141 type = lib.types.enum [ 142 "-" 143 "=" 144 "+" 145 ]; 146 default = "="; 147 description = '' 148 SRS tag separator used in generated sender addresses. 149 150 Unless you have a very good reason, you should leave this 151 setting at its default. 152 ''; 153 }; 154 155 srs-domain = lib.mkOption { 156 type = with lib.types; nullOr str; 157 default = null; 158 example = "srs.example.com"; 159 description = '' 160 Dedicated mail domain used for ephemeral SRS envelope addresses. 161 162 Recommended to configure, when hosting multiple unrelated mail 163 domains (e.g. for different customers), to prevent privacy 164 issues. 165 166 Set to `null` to not configure any `srs-domain`. 167 ''; 168 }; 169 170 socketmap = lib.mkOption { 171 type = lib.types.strMatching "^(unix|inet):.+"; 172 default = "unix:/run/postsrsd/socket"; 173 example = "inet:localhost:10003"; 174 description = '' 175 Listener configuration in socket map format native to Postfix configuration. 176 ''; 177 }; 178 179 chroot-dir = lib.mkOption { 180 type = lib.types.str; 181 default = ""; 182 readOnly = true; 183 description = '' 184 Path to chroot into at runtime as an additional layer of protection. 185 186 ::: {.note} 187 We confine the runtime environment through systemd hardening instead, so this option is read-only. 188 ::: 189 ''; 190 }; 191 192 unprivileged-user = lib.mkOption { 193 type = lib.types.str; 194 default = ""; 195 readOnly = true; 196 description = '' 197 Unprivileged user to drop privileges to. 198 199 ::: {.note} 200 Our systemd unit never runs postsrsd as a privileged process, so this option is read-only. 201 ::: 202 ''; 203 }; 204 }; 205 }; 206 default = { }; 207 description = '' 208 Configuration options for the postsrsd.conf file. 209 210 See the [example configuration](https://github.com/roehling/postsrsd/blob/${cfg.package.version}/doc/postsrsd.conf) for possible values. 211 ''; 212 }; 213 214 configurePostfix = lib.mkOption { 215 type = lib.types.bool; 216 default = true; 217 description = '' 218 Whether to configure the required settings to use postsrsd in the local Postfix instance. 219 ''; 220 }; 221 222 user = lib.mkOption { 223 type = lib.types.str; 224 default = "postsrsd"; 225 description = "User for the daemon"; 226 }; 227 228 group = lib.mkOption { 229 type = lib.types.str; 230 default = "postsrsd"; 231 description = "Group for the daemon"; 232 }; 233 }; 234 }; 235 236 config = lib.mkMerge [ 237 (lib.mkIf (cfg.enable && cfg.configurePostfix && config.services.postfix.enable) { 238 services.postfix.settings.main = { 239 # https://github.com/roehling/postsrsd#configuration 240 sender_canonical_maps = "socketmap:${cfg.settings.socketmap}:forward"; 241 sender_canonical_classes = "envelope_sender"; 242 recipient_canonical_maps = "socketmap:${cfg.settings.socketmap}:reverse"; 243 recipient_canonical_classes = [ 244 "envelope_recipient" 245 "header_recipient" 246 ]; 247 }; 248 249 users.users.postfix.extraGroups = [ cfg.group ]; 250 }) 251 252 (lib.mkIf cfg.enable { 253 users.users = lib.optionalAttrs (cfg.user == "postsrsd") { 254 postsrsd = { 255 group = cfg.group; 256 uid = config.ids.uids.postsrsd; 257 }; 258 }; 259 260 users.groups = lib.optionalAttrs (cfg.group == "postsrsd") { 261 postsrsd.gid = config.ids.gids.postsrsd; 262 }; 263 264 systemd.services.postsrsd-generate-secrets = { 265 path = [ pkgs.coreutils ]; 266 script = '' 267 if [ -e "${cfg.secretsFile}" ]; then 268 echo "Secrets file exists. Nothing to do!" 269 else 270 echo "WARNING: secrets file not found, autogenerating!" 271 DIR="$(dirname "${cfg.secretsFile}")" 272 install -m 750 -o ${cfg.user} -g ${cfg.group} -d "$DIR" 273 install -m 600 -o ${cfg.user} -g ${cfg.group} <(dd if=/dev/random bs=18 count=1 | base64) "${cfg.secretsFile}" 274 fi 275 ''; 276 serviceConfig = { 277 Type = "oneshot"; 278 }; 279 }; 280 281 environment.etc."postsrsd.conf".source = configFile; 282 283 systemd.services.postsrsd = { 284 description = "PostSRSd SRS rewriting server"; 285 after = [ 286 "network.target" 287 "postsrsd-generate-secrets.service" 288 ]; 289 before = [ "postfix.service" ]; 290 wantedBy = [ "multi-user.target" ]; 291 requires = [ "postsrsd-generate-secrets.service" ]; 292 restartTriggers = [ configFile ]; 293 294 serviceConfig = { 295 ExecStart = utils.escapeSystemdExecArgs [ 296 (lib.getExe cfg.package) 297 "-C" 298 "/etc/postsrsd.conf" 299 ]; 300 User = cfg.user; 301 Group = cfg.group; 302 RuntimeDirectory = "postsrsd"; 303 RuntimeDirectoryMode = "0750"; 304 LoadCredential = "secrets-file:${cfg.secretsFile}"; 305 306 CapabilityBoundingSet = [ "" ]; 307 LockPersonality = true; 308 MemoryDenyWriteExecute = true; 309 NoNewPrivileges = true; 310 PrivateDevices = true; 311 PrivateMounts = true; 312 PrivateNetwork = lib.hasPrefix "unix:" cfg.settings.socketmap; 313 PrivateTmp = true; 314 PrivateUsers = true; 315 ProtectControlGroups = true; 316 ProtectHome = true; 317 ProtectHostname = true; 318 ProtectKernelLogs = true; 319 ProtectKernelModules = true; 320 ProtectKernelTunables = true; 321 ProtectSystem = "strict"; 322 ProtectProc = "invisible"; 323 ProcSubset = "pid"; 324 RemoveIPC = true; 325 RestrictAddressFamilies = 326 if lib.hasPrefix "unix:" cfg.settings.socketmap then 327 [ "AF_UNIX" ] 328 else 329 [ 330 "AF_INET" 331 "AF_INET6" 332 ]; 333 RestrictNamespaces = true; 334 RestrictRealtime = true; 335 RestrictSUIDSGID = true; 336 SystemCallArchitectures = "native"; 337 SystemCallFilter = [ 338 "@system-service" 339 "~@privileged @resources" 340 ]; 341 UMask = "0027"; 342 }; 343 }; 344 }) 345 ]; 346 347 # package version referenced in option documentation 348 meta.buildDocsInSandbox = false; 349}