forked from mirrors/nixpkgs
nixos/schleuder: init module and accompanying test
Co-Authored-By: Martin Weinelt <hexa@darmstadt.ccc.de> Co-Authored-By: Cole Helbling <cole.helbling@determinate.systems>
This commit is contained in:
parent
41d5a21d6a
commit
1dabedae3e
|
@ -516,6 +516,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
|
||||
|
|
162
nixos/modules/services/mail/schleuder.nix
Normal file
162
nixos/modules/services/mail/schleuder.nix
Normal file
|
@ -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";
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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 {};
|
||||
|
|
128
nixos/tests/schleuder.nix
Normal file
128
nixos/tests/schleuder.nix
Normal file
|
@ -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")
|
||||
'';
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
, ruby
|
||||
, bundlerUpdateScript
|
||||
, defaultGemConfig
|
||||
, nixosTests
|
||||
}:
|
||||
|
||||
bundlerApp {
|
||||
|
@ -18,6 +19,9 @@ bundlerApp {
|
|||
];
|
||||
|
||||
passthru.updateScript = bundlerUpdateScript "schleuder";
|
||||
passthru.tests = {
|
||||
inherit (nixosTests) schleuder;
|
||||
};
|
||||
|
||||
meta = with lib; {
|
||||
description = "Schleuder is an encrypting mailing list manager with remailing-capabilities";
|
||||
|
|
Loading…
Reference in a new issue