From bb2ccd64087a5451114e835fcd96cb59fb9f3baa Mon Sep 17 00:00:00 2001 From: Thomas Preisner Date: Sun, 29 Dec 2024 15:47:28 +0100 Subject: [PATCH] tests: Add tests for rspamd-related functionality --- flake.nix | 2 +- tests/aliases.nix | 1 - tests/rspamd.nix | 175 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 tests/rspamd.nix diff --git a/flake.nix b/flake.nix index 3774a92..abac9b8 100644 --- a/flake.nix +++ b/flake.nix @@ -36,7 +36,7 @@ ... }: { checks = let - tests = ["internal" "basic" "aliases"]; + tests = ["internal" "basic" "aliases" "rspamd"]; genTest = testName: { "name" = testName; "value" = import (./tests + "/${testName}.nix") {inherit pkgs;}; diff --git a/tests/aliases.nix b/tests/aliases.nix index cd3aae3..842991d 100644 --- a/tests/aliases.nix +++ b/tests/aliases.nix @@ -71,7 +71,6 @@ in }; sendMail = mkSendMail smtpSettings accounts; recvMail = mkRecvMail serverAddr accounts; - cfg = nodes.server.mailsystem; in '' start_all() diff --git a/tests/rspamd.nix b/tests/rspamd.nix new file mode 100644 index 0000000..ef8e6ea --- /dev/null +++ b/tests/rspamd.nix @@ -0,0 +1,175 @@ +{pkgs, ...}: +with (import ./common/lib.nix {inherit pkgs;}); let + lib = pkgs.lib; + accounts = { + "normal" = { + address = "user@example.com"; + password = "secret-password1"; + }; + "normal2" = { + address = "user@example.org"; + password = "secret-password2;"; + }; + }; + genDkimSecret = domain: name: type: + pkgs.runCommand "mk-dkim-secrets-${domain}-${selector}" { + buildInputs = [pkgs.rspamd]; + inherit domain name type; + } '' + rspamadm dkim_keygen -d $domain -s $name -t $type ${lib.optionalString (type == "rsa") "-b 2048"} -k $out + ''; + + mkDkimSettings = domains: selectors: + lib.listToAttrs ( + map (domain: + lib.nameValuePair domain (map (entry: { + selector = entry.name; + keyFile = genDkimSecret domain entry.name entry.type; + }) + selectors)) + domains + ); +in + pkgs.nixosTest { + name = "rspamd"; + nodes = { + server = {pkgs, ...}: { + imports = [./common/server.nix]; + mailsystem = { + fqdn = "mail.example.com"; + domains = ["example.com" "example.org"]; + accounts = mkAccounts accounts; + dkimSettings = mkDkimSettings ["example.com" "example.org"] [ + { + name = "elliptic"; + type = "ed25519"; + } + { + name = "selector"; + type = "rsa"; + } + ]; + }; + }; + client = {...}: { + imports = [./common/client.nix]; + }; + }; + testScript = {nodes, ...}: let + cfg = nodes.server.mailsystem; + serverAddr = nodes.server.networking.primaryIPAddress; + clientAddr = nodes.client.networking.primaryIPAddress; + smtpSettings = { + address = serverAddr; + port = 465; + }; + sendMail = mkSendMail smtpSettings accounts; + recvMail = mkRecvMail serverAddr accounts; + test-mark-spam = accountName: + pkgs.writeScript "imap-mark-spam" '' + #!${pkgs.python3.interpreter} + import imaplib + + with imaplib.IMAP4_SSL('${serverAddr}') as imap: + imap.login('${accounts."${accountName}".address}', '${accounts."${accountName}".password}') + imap.select() + status, [response] = imap.search(None, 'ALL') + msg_ids = response.decode("utf-8").split(' ') + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 + + imap.copy(','.join(msg_ids), 'Junk') + for num in msg_ids: + imap.store(num, '+FLAGS', '\\Deleted') + imap.expunge() + + imap.select('Junk') + status, [response] = imap.search(None, 'ALL') + msg_ids = response.decode("utf-8").split(' ') + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 + + imap.close() + ''; + test-mark-ham = accountName: + pkgs.writeScript "imap-mark-ham" '' + #!${pkgs.python3.interpreter} + import imaplib + + with imaplib.IMAP4_SSL('${serverAddr}') as imap: + imap.login('${accounts."${accountName}".address}', '${accounts."${accountName}".password}') + imap.select('Junk') + status, [response] = imap.search(None, 'ALL') + msg_ids = response.decode("utf-8").split(' ') + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 + + imap.copy(','.join(msg_ids), 'INBOX') + for num in msg_ids: + imap.store(num, '+FLAGS', '\\Deleted') + imap.expunge() + + imap.select('INBOX') + status, [response] = imap.search(None, 'ALL') + msg_ids = response.decode("utf-8").split(' ') + print(msg_ids) + assert status == 'OK' + assert len(msg_ids) == 1 + + imap.close() + ''; + in '' + start_all() + + server.wait_for_unit("multi-user.target") + client.wait_for_unit("multi-user.target") + server.wait_until_succeeds("${waitForRspamd nodes.server}") + + with subtest("rspamd configuration is valid"): + server.succeed("${pkgs.rspamd}/bin/rspamadm configtest >&2") + + with subtest("rspamd rejects spam"): + client.fail("${sendMail "normal" "" accounts."normal2".address '' + Subject: GTUBE-Test + + Hello User2, + this is a mail containing a GTUBE pattern that should result in the rejection of this mail. + + XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X + ''}") + + with subtest("imap sieve junk trainer"): + client.succeed("${sendMail "normal" "" accounts."normal2".address '' + Subject: Testmail + + Hello User2, + this is a testmail. + ''}") + server.wait_until_fails('${pendingPostqueue}') + + client.succeed("${test-mark-spam "normal2"} >&2") + server.wait_until_succeeds("journalctl -u dovecot2 | grep -i learn-spam.sh >&2") + client.succeed("${test-mark-ham "normal2"} >&2") + server.wait_until_succeeds("journalctl -u dovecot2 | grep -i learn-ham.sh >&2") + + with subtest("dkim signing"): + client.succeed("${sendMail "normal2" "" accounts."normal".address '' + Subject: Testmail + + Hello User1, + this is also a testmail. + ''}") + server.wait_until_fails('${pendingPostqueue}') + client.execute("${cleanupMail}") + # fetchmail returns EXIT_CODE 0 when it retrieves mail + client.succeed("${recvMail "normal"} >&2") + + client.succeed("cat ~/mail/* >&2") + # make sure the mail has all configured dkim signatures + client.succeed("grep ${(builtins.elemAt cfg.dkimSettings."example.com" 0).selector} ~/mail/*") + client.succeed("grep ${(builtins.elemAt cfg.dkimSettings."example.com" 1).selector} ~/mail/*") + ''; + }