diff options
author | Martin Weinelt <mweinelt@users.noreply.github.com> | 2022-06-24 21:38:22 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-06-24 21:38:22 +0200 |
commit | a24431e56f3cd4b01db7d71086a782ba29ad87ab (patch) | |
tree | 80f0d3540102eb29600d72dc8099f020043e53e3 /nixos | |
parent | 5c926654849e6bdc99259d118405358a12f52b73 (diff) | |
parent | e608c54d58daa466c494a6e09daff8d4cfbed942 (diff) |
Merge pull request #162808 from mweinelt/schleuder
schleuder: init
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/doc/manual/from_md/release-notes/rl-2211.section.xml | 7 | ||||
-rw-r--r-- | nixos/doc/manual/release-notes/rl-2211.section.md | 2 | ||||
-rw-r--r-- | nixos/modules/module-list.nix | 1 | ||||
-rw-r--r-- | nixos/modules/services/mail/schleuder.nix | 162 | ||||
-rw-r--r-- | nixos/tests/all-tests.nix | 1 | ||||
-rw-r--r-- | nixos/tests/schleuder.nix | 128 |
6 files changed, 301 insertions, 0 deletions
diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml index 8b1e402fc6f44..097c2b5e77c65 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml @@ -144,6 +144,13 @@ </listitem> <listitem> <para> + <link xlink:href="https://schleuder.org/">schleuder</link>, a + mailing list manager with PGP support. Enable using + <link linkend="opt-services.schleuder.enable">services.schleuder</link>. + </para> + </listitem> + <listitem> + <para> <link xlink:href="https://www.expressvpn.com">expressvpn</link>, the CLI client for ExpressVPN. Available as <link linkend="opt-services.expressvpn.enable">services.expressvpn</link>. diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md index c2626998843bf..f79d9117abb25 100644 --- a/nixos/doc/manual/release-notes/rl-2211.section.md +++ b/nixos/doc/manual/release-notes/rl-2211.section.md @@ -60,6 +60,8 @@ In addition to numerous new and upgraded packages, this release has the followin Available as [services.infnoise](options.html#opt-services.infnoise.enable). - [persistent-evdev](https://github.com/aiberia/persistent-evdev), a daemon to add virtual proxy devices that mirror a physical input device but persist even if the underlying hardware is hot-plugged. Available as [services.persistent-evdev](#opt-services.persistent-evdev.enable). +- [schleuder](https://schleuder.org/), a mailing list manager with PGP support. Enable using [services.schleuder](#opt-services.schleuder.enable). + - [expressvpn](https://www.expressvpn.com), the CLI client for ExpressVPN. Available as [services.expressvpn](#opt-services.expressvpn.enable). <!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. --> diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 7af6d826a78f5..b0fd06e66e02f 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -515,6 +515,7 @@ ./services/mail/rspamd.nix ./services/mail/rss2email.nix ./services/mail/roundcube.nix + ./services/mail/schleuder.nix ./services/mail/sympa.nix ./services/mail/nullmailer.nix ./services/matrix/appservice-discord.nix diff --git a/nixos/modules/services/mail/schleuder.nix b/nixos/modules/services/mail/schleuder.nix new file mode 100644 index 0000000000000..7ba15f1070bde --- /dev/null +++ b/nixos/modules/services/mail/schleuder.nix @@ -0,0 +1,162 @@ +{ config, pkgs, lib, ... }: +let + cfg = config.services.schleuder; + settingsFormat = pkgs.formats.yaml { }; + postfixMap = entries: lib.concatStringsSep "\n" (lib.mapAttrsToList (name: value: "${name} ${value}") entries); + writePostfixMap = name: entries: pkgs.writeText name (postfixMap entries); + configScript = pkgs.writeScript "schleuder-cfg" '' + #!${pkgs.runtimeShell} + set -exuo pipefail + umask 0077 + ${pkgs.yq}/bin/yq \ + --slurpfile overrides <(${pkgs.yq}/bin/yq . <${lib.escapeShellArg cfg.extraSettingsFile}) \ + < ${settingsFormat.generate "schleuder.yml" cfg.settings} \ + '. * $overrides[0]' \ + > /etc/schleuder/schleuder.yml + chown schleuder: /etc/schleuder/schleuder.yml + ''; +in +{ + options.services.schleuder = { + enable = lib.mkEnableOption "Schleuder secure remailer"; + enablePostfix = lib.mkEnableOption "automatic postfix integration" // { default = true; }; + lists = lib.mkOption { + description = '' + List of list addresses that should be handled by Schleuder. + + Note that this is only handled by the postfix integration, and + the setup of the lists, their members and their keys has to be + performed separately via schleuder's API, using a tool such as + schleuder-cli. + ''; + type = lib.types.listOf lib.types.str; + default = [ ]; + example = [ "widget-team@example.com" "security@example.com" ]; + }; + /* maybe one day.... + domains = lib.mkOption { + description = "Domains for which all mail should be handled by Schleuder."; + type = lib.types.listOf lib.types.str; + default = []; + example = ["securelists.example.com"]; + }; + */ + settings = lib.mkOption { + description = '' + Settings for schleuder.yml. + + Check the <link xlink:href="https://0xacab.org/schleuder/schleuder/blob/master/etc/schleuder.yml">example configuration</link> for possible values. + ''; + type = lib.types.submodule { + freeformType = settingsFormat.type; + options.keyserver = lib.mkOption { + type = lib.types.str; + description = '' + Key server from which to fetch and update keys. + + Note that NixOS uses a different default from upstream, since the upstream default sks-keyservers.net is deprecated. + ''; + default = "keys.openpgp.org"; + }; + }; + default = { }; + }; + extraSettingsFile = lib.mkOption { + description = "YAML file to merge into the schleuder config at runtime. This can be used for secrets such as API keys."; + type = lib.types.nullOr lib.types.path; + default = null; + }; + listDefaults = lib.mkOption { + description = '' + Default settings for lists (list-defaults.yml). + + Check the <link xlink:href="https://0xacab.org/schleuder/schleuder/-/blob/master/etc/list-defaults.yml">example configuration</link> for possible values. + ''; + type = settingsFormat.type; + default = { }; + }; + }; + config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = !(cfg.settings.api ? valid_api_keys); + message = '' + services.schleuder.settings.api.valid_api_keys is set. Defining API keys via NixOS config results in them being copied to the world-readable Nix store. Please use the extraSettingsFile option to store API keys in a non-public location. + ''; + } + { + assertion = !(lib.any (db: db ? password) (lib.attrValues cfg.settings.database or {})); + message = '' + A password is defined for at least one database in services.schleuder.settings.database. Defining passwords via NixOS config results in them being copied to the world-readable Nix store. Please use the extraSettingsFile option to store database passwords in a non-public location. + ''; + } + ]; + users.users.schleuder.isSystemUser = true; + users.users.schleuder.group = "schleuder"; + users.groups.schleuder = {}; + environment.systemPackages = [ + pkgs.schleuder-cli + ]; + services.postfix = lib.mkIf cfg.enablePostfix { + extraMasterConf = '' + schleuder unix - n n - - pipe + flags=DRhu user=schleuder argv=/${pkgs.schleuder}/bin/schleuder work ''${recipient} + ''; + transport = lib.mkIf (cfg.lists != [ ]) (postfixMap (lib.genAttrs cfg.lists (_: "schleuder:"))); + extraConfig = '' + schleuder_destination_recipient_limit = 1 + ''; + # review: does this make sense? + localRecipients = lib.mkIf (cfg.lists != [ ]) cfg.lists; + }; + systemd.services = let commonServiceConfig = { + # We would have liked to use DynamicUser, but since the default + # database is SQLite and lives in StateDirectory, and that same + # database needs to be readable from the postfix service, this + # isn't trivial to do. + User = "schleuder"; + StateDirectory = "schleuder"; + StateDirectoryMode = "0700"; + }; in + { + schleuder-init = { + serviceConfig = commonServiceConfig // { + ExecStartPre = lib.mkIf (cfg.extraSettingsFile != null) [ + "+${configScript}" + ]; + ExecStart = [ "${pkgs.schleuder}/bin/schleuder install" ]; + Type = "oneshot"; + }; + }; + schleuder-api-daemon = { + after = [ "local-fs.target" "network.target" "schleuder-init.service" ]; + wantedBy = [ "multi-user.target" ]; + requires = [ "schleuder-init.service" ]; + serviceConfig = commonServiceConfig // { + ExecStart = [ "${pkgs.schleuder}/bin/schleuder-api-daemon" ]; + }; + }; + schleuder-weekly-key-maintenance = { + after = [ "local-fs.target" "network.target" ]; + startAt = "weekly"; + serviceConfig = commonServiceConfig // { + ExecStart = [ + "${pkgs.schleuder}/bin/schleuder refresh_keys" + "${pkgs.schleuder}/bin/schleuder check_keys" + ]; + }; + }; + }; + + environment.etc."schleuder/schleuder.yml" = lib.mkIf (cfg.extraSettingsFile == null) { + source = settingsFormat.generate "schleuder.yml" cfg.settings; + }; + environment.etc."schleuder/list-defaults.yml".source = settingsFormat.generate "list-defaults.yml" cfg.listDefaults; + + services.schleuder = { + #lists_dir = "/var/lib/schleuder.lists"; + settings.filters_dir = lib.mkDefault "/var/lib/schleuder/filters"; + settings.keyword_handlers_dir = lib.mkDefault "/var/lib/schleuder/keyword_handlers"; + }; + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index fa88ad524070c..5e4325a1739fc 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -485,6 +485,7 @@ in { samba = handleTest ./samba.nix {}; samba-wsdd = handleTest ./samba-wsdd.nix {}; sanoid = handleTest ./sanoid.nix {}; + schleuder = handleTest ./schleuder.nix {}; sddm = handleTest ./sddm.nix {}; seafile = handleTest ./seafile.nix {}; searx = handleTest ./searx.nix {}; diff --git a/nixos/tests/schleuder.nix b/nixos/tests/schleuder.nix new file mode 100644 index 0000000000000..a9e4cc325bc76 --- /dev/null +++ b/nixos/tests/schleuder.nix @@ -0,0 +1,128 @@ +let + certs = import ./common/acme/server/snakeoil-certs.nix; + domain = certs.domain; +in +import ./make-test-python.nix { + name = "schleuder"; + nodes.machine = { pkgs, ... }: { + imports = [ ./common/user-account.nix ]; + services.postfix = { + enable = true; + enableSubmission = true; + tlsTrustedAuthorities = "${certs.ca.cert}"; + sslCert = "${certs.${domain}.cert}"; + sslKey = "${certs.${domain}.key}"; + inherit domain; + destination = [ domain ]; + localRecipients = [ "root" "alice" "bob" ]; + }; + services.schleuder = { + enable = true; + # Don't do it like this in production! The point of this setting + # is to allow loading secrets from _outside_ the world-readable + # Nix store. + extraSettingsFile = pkgs.writeText "schleuder-api-keys.yml" '' + api: + valid_api_keys: + - fnord + ''; + lists = [ "security@${domain}" ]; + settings.api = { + tls_cert_file = "${certs.${domain}.cert}"; + tls_key_file = "${certs.${domain}.key}"; + }; + }; + + environment.systemPackages = [ + pkgs.gnupg + pkgs.msmtp + (pkgs.writeScriptBin "do-test" '' + #!${pkgs.runtimeShell} + set -exuo pipefail + + # Generate a GPG key with no passphrase and export it + sudo -u alice gpg --passphrase-fd 0 --batch --yes --quick-generate-key 'alice@${domain}' rsa4096 sign,encr < <(echo) + sudo -u alice gpg --armor --export alice@${domain} > alice.asc + # Create a new mailing list with alice as the owner, and alice's key + schleuder-cli list new security@${domain} alice@${domain} alice.asc + + # Send an email from a non-member of the list. Use --auto-from so we don't have to specify who it's from twice. + msmtp --auto-from security@${domain} --host=${domain} --port=25 --tls --tls-starttls <<EOF + Subject: really big security issue!! + From: root@${domain} + + I found a big security problem! + EOF + + # Wait for delivery + (set +o pipefail; journalctl -f -n 1000 -u postfix | grep -m 1 'delivered to maildir') + + # There should be exactly one email + mail=(/var/spool/mail/alice/new/*) + [[ "''${#mail[@]}" = 1 ]] + + # Find the fingerprint of the mailing list key + read list_key_fp address < <(schleuder-cli keys list security@${domain} | grep security@) + schleuder-cli keys export security@${domain} $list_key_fp > list.asc + + # Import the key into alice's keyring, so we can verify it as well as decrypting + sudo -u alice gpg --import <list.asc + # And perform the decryption. + sudo -u alice gpg -d $mail >decrypted + # And check that the text matches. + grep "big security problem" decrypted + '') + + # For debugging: + # pkgs.vim pkgs.openssl pkgs.sqliteinteractive + ]; + + security.pki.certificateFiles = [ certs.ca.cert ]; + + # Since we don't have internet here, use dnsmasq to provide MX records from /etc/hosts + services.dnsmasq = { + enable = true; + extraConfig = '' + selfmx + ''; + }; + + networking.extraHosts = '' + 127.0.0.1 ${domain} + ''; + + # schleuder-cli's config is not quite optimal in several ways: + # - A fingerprint _must_ be pinned, it doesn't even have an option + # to trust the PKI + # - It compares certificate fingerprints rather than key + # fingerprints, so renewals break the pin (though that's not + # relevant for this test) + # - It compares them as strings, which means we need to match the + # expected format exactly. This means removing the :s and + # lowercasing it. + # Refs: + # https://0xacab.org/schleuder/schleuder-cli/-/issues/16 + # https://0xacab.org/schleuder/schleuder-cli/-/blob/f8895b9f47083d8c7b99a2797c93f170f3c6a3c0/lib/schleuder-cli/helper.rb#L230-238 + systemd.tmpfiles.rules = let cliconfig = pkgs.runCommand "schleuder-cli.yml" + { + nativeBuildInputs = [ pkgs.jq pkgs.openssl ]; + } '' + fp=$(openssl x509 -in ${certs.${domain}.cert} -noout -fingerprint -sha256 | cut -d = -f 2 | tr -d : | tr 'A-Z' 'a-z') + cat > $out <<EOF + host: localhost + port: 4443 + tls_fingerprint: "$fp" + api_key: fnord + EOF + ''; in + [ + "L+ /root/.schleuder-cli/schleuder-cli.yml - - - - ${cliconfig}" + ]; + }; + + testScript = '' + machine.wait_for_unit("multi-user.target") + machine.wait_until_succeeds("nc -z localhost 4443") + machine.succeed("do-test") + ''; +} |