{ 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; services.openssh = { enable = true; settings.PermitRootLogin = "yes"; }; environment.systemPackages = with pkgs;[ fetchmail msmtp procmail (writeScriptBin "mail-check" '' ${python3}/bin/python ${../scripts/mail-check.py} $@ '') ]; environment.etc = { bind-password.text = bindPassword; "root/.fetchmailrc" = { text = '' poll 127.0.0.1 with proto IMAP user 'bob@example.com' there with password '${bobPassword}' is 'root' here mda procmail ''; mode = "0700"; }; "root/.procmailrc" = { text = "DEFAULT=$HOME/mail"; }; "root/.msmtprc" = { text = '' account alice host 127.0.0.1 port 587 from alice@example.com user alice@example.com password ${alicePassword} ''; }; "root/email1".text = '' Message-ID: <238902fy@host.local.network> From: Alice To: Bob Cc: Bcc: Subject: This is a test Email from Alice to Bob Reply-To: Hello Bob, I hope this mail reaches you safely. ''; }; 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/example"; 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"; passwordFile = "/etc/bind-password"; }; searchBase = "ou=users,dc=example"; searchScope = "sub"; }; vmailGroupName = "vmail"; vmailUID = 5000; enableImap = true; }; }; }; testScript = '' import sys import re machine.start() machine.wait_for_unit("multi-user.target") machine.execute("cp -p /etc/root/.* ~/") machine.succeed("cat ~/.fetchmailrc >&2") machine.succeed("cat ~/.procmailrc >&2") machine.succeed("cat ~/.msmtprc >&2") # This function retrieves the ldap table file from a postconf # command. # A key lookup is achived and the returned value is compared # to the expected value. def test_lookup(postconf_cmdline, key, expected): conf = machine.succeed(postconf_cmdline).rstrip() ldap_table_path = re.match('.* =.*ldap:(.*)', conf).group(1) value = machine.succeed(f"postmap -q {key} ldap:{ldap_table_path}").rstrip() try: assert value == expected except AssertionError: print(f"Expected {conf} lookup for key '{key}' to return '{expected}, but got '{value}'", file=sys.stderr) raise with subtest("Test postmap lookups"): test_lookup("postconf virtual_mailbox_maps", "alice@example.com", "alice@example.com") test_lookup("postconf -P submission/inet/smtpd_sender_login_maps", "alice@example.com", "alice@example.com") test_lookup("postconf virtual_mailbox_maps", "bob@example.com", "bob@example.com") test_lookup("postconf -P submission/inet/smtpd_sender_login_maps", "bob@example.com", "bob@example.com") with subtest("Test doveadm lookups"): machine.succeed("doveadm user -u alice@example.com") machine.succeed("doveadm user -u bob@example.com") with subtest("Files containing secrets are only readable by root"): machine.succeed("ls -l /run/postfix/*.cf | grep -e '-rw------- 1 root root'") machine.succeed("ls -l /run/dovecot2/dovecot-ldap.conf.ext | grep -e '-rw------- 1 root root'") 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@example.com", "--imap-host localhost", "--imap-username bob@example.com", "--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@example.com'") with subtest("Test mail delivery"): machine.succeed(" ".join([ "mail-check send-and-read", "--smtp-port 587", "--smtp-starttls", "--smtp-host localhost", "--smtp-username alice@example.com", "--imap-host localhost", "--imap-username bob@example.com", "--from-addr alice@example.com", "--to-addr bob@example.com", "--src-password-file <(echo '${alicePassword}')", "--dst-password-file <(echo '${bobPassword}')", "--ignore-dkim-spf" ])) with subtest("Test mail properties"): machine.succeed( "msmtp -a alice --tls=on --tls-certcheck=off --auth=on bob@example.com < /etc/root/email1" ) machine.execute("rm ~/mail/* >&2") machine.wait_until_fails('[ "$(postqueue -p)" != "Mail queue is empty" ]') machine.succeed("fetchmail --nosslcertck -v >&2") machine.log(machine.succeed("ls -lah ~/mail/")) machine.succeed("cat ~/mail/* >&2") # Make sure virtual accounts get DKIM signed machine.succeed("grep DKIM-Signature: ~/mail/*") ''; }