a87d01ea79
Now that we have more experience with how we use the LDAP module options we can make smarter decisions in how to organize them. We can also explain much better what these options imply, which results in more extensive option documentation.
252 lines
8.5 KiB
Nix
252 lines
8.5 KiB
Nix
let
|
|
bindPassword = "unsafegibberish";
|
|
alicePassword = "testalice";
|
|
bobPassword = "testbob";
|
|
in
|
|
{
|
|
name = "ldap";
|
|
|
|
nodes = {
|
|
machine =
|
|
{ pkgs, ... }:
|
|
{
|
|
imports = [
|
|
./../default.nix
|
|
./lib/config.nix
|
|
];
|
|
|
|
virtualisation.memorySize = 1024;
|
|
|
|
services.openssh = {
|
|
enable = true;
|
|
settings.PermitRootLogin = "yes";
|
|
};
|
|
|
|
environment.systemPackages = [
|
|
(pkgs.writeScriptBin "mail-check" ''
|
|
${pkgs.python3}/bin/python ${../scripts/mail-check.py} $@
|
|
'')
|
|
];
|
|
|
|
environment.etc.bind-password.text = bindPassword;
|
|
|
|
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" =
|
|
#ldif
|
|
''
|
|
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
|
|
entryUUID: c52f777b-a6e8-4507-80f9-c4de47e8520d
|
|
objectClass: inetOrgPerson
|
|
cn: alice
|
|
sn: Foo
|
|
mail: alice@example.com
|
|
userPassword: ${alicePassword}
|
|
|
|
dn: cn=bob,ou=users,dc=example
|
|
entryUUID: f3b4e8ea-087f-42cc-95f0-cbfd99386092
|
|
objectClass: inetOrgPerson
|
|
objectClass: posixAccount
|
|
cn: bob
|
|
uid: bob
|
|
uidNumber: 9999
|
|
gidNumber: 9999
|
|
sn: Bar
|
|
mail: bob@example.com
|
|
homeDirectory: /home/bob
|
|
userPassword: ${bobPassword}
|
|
'';
|
|
};
|
|
|
|
mailserver = {
|
|
enable = true;
|
|
fqdn = "mail.example.com";
|
|
domains = [ "example.com" ];
|
|
localDnsResolver = false;
|
|
indexDir = "/var/lib/dovecot/indices";
|
|
|
|
ldap = {
|
|
enable = true;
|
|
uris = [
|
|
"ldap://"
|
|
];
|
|
bind = {
|
|
dn = "cn=mail,dc=example";
|
|
passwordFile = "/etc/bind-password";
|
|
};
|
|
base = "ou=users,dc=example";
|
|
scope = "sub";
|
|
};
|
|
|
|
forwards = {
|
|
"bob_fw@example.com" = "bob@example.com";
|
|
};
|
|
|
|
vmailGroupName = "vmail";
|
|
vmailUID = 5000;
|
|
|
|
enableImap = false;
|
|
};
|
|
};
|
|
};
|
|
testScript =
|
|
{
|
|
nodes,
|
|
...
|
|
}:
|
|
# python
|
|
''
|
|
import sys
|
|
import re
|
|
|
|
machine.start()
|
|
machine.wait_for_unit("multi-user.target")
|
|
|
|
# if the schema is broken, fail fast. helps during development.
|
|
machine.wait_for_unit("openldap.service")
|
|
|
|
# TODO put this blocking into the systemd units?
|
|
machine.wait_until_succeeds(
|
|
"set +e; timeout 1 nc -U /run/rspamd/rspamd-milter.sock < /dev/null; [ $? -eq 124 ]"
|
|
)
|
|
|
|
# This function retrieves the ldap table file from a postconf
|
|
# command.
|
|
# A key lookup is achieved 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")
|
|
|
|
machine.succeed("doveadm user -f uid bob@example.com | grep ${toString nodes.machine.mailserver.vmailUID}")
|
|
machine.succeed("doveadm user -f gid bob@example.com | grep ${toString nodes.machine.mailserver.vmailUID}")
|
|
|
|
machine.succeed("doveadm user -f home bob@example.com | grep ${nodes.machine.mailserver.mailDirectory}/ldap/f3b4e8ea-087f-42cc-95f0-cbfd99386092")
|
|
machine.succeed("doveadm user -f mail bob@example.com | grep 'maildir:~/mail:INDEX=${nodes.machine.mailserver.indexDir}/ldap/f3b4e8ea-087f-42cc-95f0-cbfd99386092'")
|
|
|
|
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 via explicit TLS"):
|
|
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 via implicit TLS"):
|
|
machine.succeed(" ".join([
|
|
"mail-check send-and-read",
|
|
"--smtp-port 465",
|
|
"--smtp-ssl",
|
|
"--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 forwarding via explicit TLS works"):
|
|
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_fw@example.com",
|
|
"--src-password-file <(echo '${alicePassword}')",
|
|
"--dst-password-file <(echo '${bobPassword}')",
|
|
"--ignore-dkim-spf"
|
|
]))
|
|
|
|
with subtest("Test cannot send mail via implicit TLS from forwarded address"):
|
|
machine.fail(" ".join([
|
|
"mail-check send-and-read",
|
|
"--smtp-port 465",
|
|
"--smtp-ssl",
|
|
"--smtp-host localhost",
|
|
"--smtp-username bob@example.com",
|
|
"--imap-host localhost",
|
|
"--imap-username alice@example.com",
|
|
"--from-addr bob_fw@example.com",
|
|
"--to-addr alice@example.com",
|
|
"--src-password-file <(echo '${bobPassword}')",
|
|
"--dst-password-file <(echo '${alicePassword}')",
|
|
"--ignore-dkim-spf"
|
|
]))
|
|
machine.succeed("journalctl -u postfix | grep -q 'Sender address rejected: not owned by user bob@example.com'")
|
|
|
|
'';
|
|
}
|