Unverified Commit 1b59fd67 authored by Martin Weinelt's avatar Martin Weinelt Committed by GitHub
Browse files

nixos/postfix-tlspol: init (#415482)

* pkgs.formats.yaml_1_2: init

Same as YAML 1.1 but relies on the unpinned remarshal version which emits
YAML 1.2.

* nixos/postfix-tlspol: init

MTA-STS and DANE/TLSA resolver and TLS policy socketmap server for
Postfix.

* nixos/tests/postfix-tlspol: init

Simple test if the service comes up and the CLI can interact with it and
gives reasonable results.
parents 8d7956b6 2288aab1
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -28,6 +28,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).
+1 −0
Original line number Diff line number Diff line
@@ -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
+220 −0
Original line number Diff line number Diff line
{
  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.
        <https://github.com/Zuplu/postfix-tlspol/blob/main/configs/config.default.yaml>
      '';
    };

    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";
      };
    };
  };
}
+1 −0
Original line number Diff line number Diff line
@@ -1103,6 +1103,7 @@ in
  postfix-raise-smtpd-tls-security-level =
    handleTest ./postfix-raise-smtpd-tls-security-level.nix
      { };
  postfix-tlspol = runTest ./postfix-tlspol.nix;
  postfixadmin = runTest ./postfixadmin.nix;
  postgres-websockets = runTest ./postgres-websockets.nix;
  postgresql = handleTest ./postgresql { };
+29 −0
Original line number Diff line number Diff line
{
  lib,
  ...
}:
{
  name = "postfix-tlspol";

  meta.maintainers = with lib.maintainers; [ hexa ];

  nodes.machine = {
    services.postfix-tlspol.enable = true;
  };

  enableOCR = true;

  testScript = ''
    import json

    machine.wait_for_unit("postfix-tlspol.service")

    with subtest("Interact with the service"):
      machine.succeed("postfix-tlspol -purge")

      response = json.loads((machine.succeed("postfix-tlspol -query localhost")))
      machine.log(json.dumps(response, indent=2))

  '';

}
Loading