From 8b03ae5701a672186c704be70bff8390b662c323 Mon Sep 17 00:00:00 2001 From: Martin Weinelt Date: Fri, 12 Aug 2022 18:54:49 +0200 Subject: [PATCH] Create LDAP test Sets up a declaratively configured OpenLDAP instance with users alice and bob. They each own one email address, First we test that postfix can communicate with LDAP and do the expected lookups using the defined maps. Then we use doveadm to make sure it can look up the two accounts. Next we check the binding between account and mail address, by logging in as alice and trying to send from bob@example.com, which alice is not allowed to do. We expect postfix to reject the sender address here. Finally we check mail delivery between alice and bob. Alice tries to send a mail from alice@example.com to bob@example.com and bob then checks whether it arrived in their mailbox. --- flake.nix | 1 + tests/ldap.nix | 172 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 tests/ldap.nix diff --git a/flake.nix b/flake.nix index 0deb9c8..54d747e 100644 --- a/flake.nix +++ b/flake.nix @@ -35,6 +35,7 @@ "external" "clamav" "multiple" + "ldap" ]; genTest = testName: release: { "name"= "${testName}-${builtins.replaceStrings ["."] ["_"] release.name}"; diff --git a/tests/ldap.nix b/tests/ldap.nix new file mode 100644 index 0000000..6077543 --- /dev/null +++ b/tests/ldap.nix @@ -0,0 +1,172 @@ +{ pkgs ? import {} +, ... +}: + +let + bindPassword = "unsafegibberish"; + alicePassword = "testalice"; + bobPassword = "testbob"; +in +pkgs.nixosTest { + name = "ldap"; + nodes = { + machine = { config, pkgs, ... }: { + imports = [ + ./../default.nix + ./lib/config.nix + ]; + + virtualisation.memorySize = 1024; + + environment.systemPackages = [ + (pkgs.writeScriptBin "mail-check" '' + ${pkgs.python3}/bin/python ${../scripts/mail-check.py} $@ + '')]; + + services.openldap = { + enable = true; + settings = { + children = { + "cn=schema".includes = [ + "${pkgs.openldap}/etc/schema/core.ldif" + "${pkgs.openldap}/etc/schema/cosine.ldif" + "${pkgs.openldap}/etc/schema/inetorgperson.ldif" + "${pkgs.openldap}/etc/schema/nis.ldif" + ]; + "olcDatabase={1}mdb" = { + attrs = { + objectClass = [ + "olcDatabaseConfig" + "olcMdbConfig" + ]; + olcDatabase = "{1}mdb"; + olcDbDirectory = "/var/lib/openldap"; + olcSuffix = "dc=example"; + }; + }; + }; + }; + declarativeContents."dc=example" = '' + dn: dc=example + objectClass: domain + dc: example + + dn: cn=mail,dc=example + objectClass: organizationalRole + objectClass: simpleSecurityObject + objectClass: top + cn: mail + userPassword: ${bindPassword} + + dn: ou=users,dc=example + objectClass: organizationalUnit + ou: users + + dn: cn=alice,ou=users,dc=example + objectClass: inetOrgPerson + cn: alice + sn: Foo + mail: alice@example.com + userPassword: ${alicePassword} + + dn: cn=bob,ou=users,dc=example + objectClass: inetOrgPerson + cn: bob + sn: Bar + mail: bob@example.com + userPassword: ${bobPassword} + ''; + }; + + mailserver = { + enable = true; + fqdn = "mail.example.com"; + domains = [ "example.com" ]; + localDnsResolver = false; + + ldap = { + enable = true; + uris = [ + "ldap://" + ]; + bind = { + dn = "cn=mail,dc=example"; + password = bindPassword; + }; + searchBase = "ou=users,dc=example"; + searchScope = "sub"; + }; + + vmailGroupName = "vmail"; + vmailUID = 5000; + + enableImap = false; + }; + }; + }; + testScript = '' + import sys + + from glob import glob + + machine.start() + machine.wait_for_unit("multi-user.target") + + def test_lookup(map, key, expected): + path = glob(f"/nix/store/*-{map}")[0] + value = machine.succeed(f"postmap -q alice@example.com ldap:{path}").rstrip() + try: + assert value == expected + except AssertionError: + print(f"Expected {map} lookup for key '{key}' to return '{expected}, but got '{value}'", file=sys.stderr) + raise + + + with subtest("Test postmap lookups"): + test_lookup("ldap-virtual-mailbox-map.cf", "alice@example.com", "alice") + test_lookup("ldap-sender-login-map.cf", "alice", "alice") + + test_lookup("ldap-virtual-mailbox-map.cf", "bob@example.com", "alice") + test_lookup("ldap-sender-login-map.cf", "bob", "alice") + + with subtest("Test doveadm lookups"): + out = machine.succeed("doveadm user -u alice") + machine.log(out) + + out = machine.succeed("doveadm user -u bob") + machine.log(out) + + with subtest("Test account/mail address binding"): + machine.fail(" ".join([ + "mail-check send-and-read", + "--smtp-port 587", + "--smtp-starttls", + "--smtp-host localhost", + "--smtp-username alice", + "--imap-host localhost", + "--imap-username bob", + "--from-addr bob@example.com", + "--to-addr aliceb@example.com", + "--src-password-file <(echo '${alicePassword}')", + "--dst-password-file <(echo '${bobPassword}')", + "--ignore-dkim-spf" + ])) + machine.succeed("journalctl -u postfix | grep -q 'Sender address rejected: not owned by user alice'") + + with subtest("Test mail delivery"): + machine.succeed(" ".join([ + "mail-check send-and-read", + "--smtp-port 587", + "--smtp-starttls", + "--smtp-host localhost", + "--smtp-username alice", + "--imap-host localhost", + "--imap-username bob", + "--from-addr alice@example.com", + "--to-addr bob@example.com", + "--src-password-file <(echo '${alicePassword}')", + "--dst-password-file <(echo '${bobPassword}')", + "--ignore-dkim-spf" + ])) + ''; +}