Tools: Integrating a local mail server into my LDAP lab

Tools: Integrating a local mail server into my LDAP lab

Introduction

Pre-Requisites

Setup Process

Mail Server Install

Postfix Config

Local CA Config on Mail Server

Dovecot TLS Config

Dovecot LDAP Config

Dovecot Mail Storage and Postfix Integration

Verification

Sending mail

Retrieving mail over IMAPS

Thunderbird Verification

adoe sends an email to jsmith

jsmith opens it

jsmith sends a reply email to adoe

adoe views the reply email

Conclusion GitHub Repo: @ShobanChiddarth/postfix-dovecot-mailserver-in-openldap-lab This is the second stage of my OpenLDAP home lab. I set up a mail server to work with existing LDAP configuration. In the existing setup, I cloned another debian server VM, gave it static IP 192.168.57.7, set its hostname to mail-server.acme.internal and then added the DNS record in Pi-hole. Then I went ahead and installed the required packages for postfix and dovecot to work with LDAP. Then I chose "Internet Site" in the postfix install prompt asking about which type of mail server to install and set the domain to acme.internal. I edited /etc/postfix/main.cf to have this configuration: mailbox_transport hands mail delivery off to Dovecot via LMTP. The smtpd_sasl_* settings let Postfix use Dovecot for SMTP authentication. local_recipient_maps tells Postfix to look up valid recipients in LDAP instead of the local user table - without this, Postfix rejects mail to LDAP users with "User unknown in local recipient table". I created /etc/postfix/ldap-recipients.cf for the recipient lookup: Then reloaded Postfix: I copied rootCA.pem that I created earlier to this server and trusted it. I generated a TLS cert for mail-server.acme.internal using mkcert and copied it to the server: Then updated /etc/dovecot/conf.d/10-ssl.conf: I changed dovecot auth settings to use LDAP auth instead of system auth by editing /etc/dovecot/conf.d/10-auth.conf - commented out auth-system.conf.ext and uncommented auth-ldap.conf.ext. Dovecot 2.4 changed the config syntax from older versions, so the standard examples online did not work. After checking the example config the package ships with, I set /etc/dovecot/conf.d/auth-ldap.conf.ext to: This reuses the same read-only service account from the LDAP lab. ldap_bind = yes means Dovecot verifies passwords by actually binding to LDAP as the user rather than fetching and comparing the hash. The userdb block maps all mail storage to a dedicated vmail system user instead of running as the LDAP user's UID. Note: ldap_auth_dn_password is stored in plaintext, but /etc/dovecot/conf.d/auth-ldap.conf.ext is owned by root and not readable by other users. Since LDAP users don't have home directories on the mail server, I created a dedicated vmail system user to own all mailboxes and set up a central mail storage directory: I updated /etc/dovecot/conf.d/10-mail.conf to use this path: Then I configured the LMTP and auth unix sockets in /etc/dovecot/conf.d/10-master.conf so Postfix can hand off incoming mail to Dovecot and use Dovecot for SMTP authentication. In the service lmtp block: In the service auth block: Then restarted Dovecot: I sent a test mail from jsmith to adoe over SMTP from a desktop VM on the same network: Postfix accepted it and delivered it to Dovecot via LMTP: Verified the mail landed in adoe's mailbox on the server: I verified that adoe can authenticate and retrieve mail over IMAPS using their LDAP credentials: TLS handshake succeeded using the mkcert CA, LDAP authentication worked, and the INBOX is accessible. Thunderbird has its own certificate store separate from the system trust store, so I had to manually import rootCA.pem into Thunderbird settings before it would trust the IMAPS connection. Postfix and Dovecot are now running as a mail server for acme.internal. Users defined in the OpenLDAP directory can send and receive mail using their LDAP credentials. Postfix looks up valid recipients in LDAP so it accepts mail for LDAP users. Dovecot authenticates over LDAPS using the same read-only service account from the LDAP lab and stores mail in a central directory owned by a dedicated vmail user. IMAP access is over TLS only. This mail server will be used in the next part of this series as the SMTP backend for Keycloak notifications. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

$ -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install -y postfix postfix-ldap dovecot-core dovecot-imapd dovecot-ldap dovecot-lmtpd -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install -y postfix postfix-ldap dovecot-core dovecot-imapd dovecot-ldap dovecot-lmtpd -weight: 600;">sudo -weight: 500;">apt -weight: 500;">install -y postfix postfix-ldap dovecot-core dovecot-imapd dovecot-ldap dovecot-lmtpd myhostname = mail-server.acme.internal mydomain = acme.internal mydestination = $myhostname, $mydomain, localhost mynetworks = 192.168.57.0/24 127.0.0.0/8 inet_interfaces = all mailbox_transport = lmtp:unix:private/dovecot-lmtp smtpd_sasl_type = dovecot smtpd_sasl_path = private/auth smtpd_sasl_auth_enable = yes local_recipient_maps = ldap:/etc/postfix/ldap-recipients.cf myhostname = mail-server.acme.internal mydomain = acme.internal mydestination = $myhostname, $mydomain, localhost mynetworks = 192.168.57.0/24 127.0.0.0/8 inet_interfaces = all mailbox_transport = lmtp:unix:private/dovecot-lmtp smtpd_sasl_type = dovecot smtpd_sasl_path = private/auth smtpd_sasl_auth_enable = yes local_recipient_maps = ldap:/etc/postfix/ldap-recipients.cf myhostname = mail-server.acme.internal mydomain = acme.internal mydestination = $myhostname, $mydomain, localhost mynetworks = 192.168.57.0/24 127.0.0.0/8 inet_interfaces = all mailbox_transport = lmtp:unix:private/dovecot-lmtp smtpd_sasl_type = dovecot smtpd_sasl_path = private/auth smtpd_sasl_auth_enable = yes local_recipient_maps = ldap:/etc/postfix/ldap-recipients.cf server_host = ldaps://ldap-server.acme.internal bind = yes bind_dn = cn=sssd,ou=-weight: 500;">service-accounts,dc=acme,dc=internal bind_pw = SSSDPass123 search_base = ou=users,dc=acme,dc=internal query_filter = (&(objectClass=posixAccount)(mail=%s)) result_attribute = mail server_host = ldaps://ldap-server.acme.internal bind = yes bind_dn = cn=sssd,ou=-weight: 500;">service-accounts,dc=acme,dc=internal bind_pw = SSSDPass123 search_base = ou=users,dc=acme,dc=internal query_filter = (&(objectClass=posixAccount)(mail=%s)) result_attribute = mail server_host = ldaps://ldap-server.acme.internal bind = yes bind_dn = cn=sssd,ou=-weight: 500;">service-accounts,dc=acme,dc=internal bind_pw = SSSDPass123 search_base = ou=users,dc=acme,dc=internal query_filter = (&(objectClass=posixAccount)(mail=%s)) result_attribute = mail -weight: 600;">sudo -weight: 500;">systemctl reload postfix -weight: 600;">sudo -weight: 500;">systemctl reload postfix -weight: 600;">sudo -weight: 500;">systemctl reload postfix debian@mail-server:~/acme-certs$ ls rootCA.pem debian@mail-server:~/acme-certs$ -weight: 600;">sudo cp rootCA.pem /usr/local/share/ca-certificates/acme-rootCA.crt [-weight: 600;">sudo] password for debian: debian@mail-server:~/acme-certs$ -weight: 600;">sudo -weight: 500;">update-ca-certificates Updating certificates in /etc/ssl/certs... rehash: warning: skipping ca-certificates.crt, it does not contain exactly one certificate or CRL 1 added, 0 removed; done. Running hooks in /etc/ca-certificates/-weight: 500;">update.d... done. debian@mail-server:~/acme-certs$ debian@mail-server:~/acme-certs$ ls rootCA.pem debian@mail-server:~/acme-certs$ -weight: 600;">sudo cp rootCA.pem /usr/local/share/ca-certificates/acme-rootCA.crt [-weight: 600;">sudo] password for debian: debian@mail-server:~/acme-certs$ -weight: 600;">sudo -weight: 500;">update-ca-certificates Updating certificates in /etc/ssl/certs... rehash: warning: skipping ca-certificates.crt, it does not contain exactly one certificate or CRL 1 added, 0 removed; done. Running hooks in /etc/ca-certificates/-weight: 500;">update.d... done. debian@mail-server:~/acme-certs$ debian@mail-server:~/acme-certs$ ls rootCA.pem debian@mail-server:~/acme-certs$ -weight: 600;">sudo cp rootCA.pem /usr/local/share/ca-certificates/acme-rootCA.crt [-weight: 600;">sudo] password for debian: debian@mail-server:~/acme-certs$ -weight: 600;">sudo -weight: 500;">update-ca-certificates Updating certificates in /etc/ssl/certs... rehash: warning: skipping ca-certificates.crt, it does not contain exactly one certificate or CRL 1 added, 0 removed; done. Running hooks in /etc/ca-certificates/-weight: 500;">update.d... done. debian@mail-server:~/acme-certs$ -weight: 600;">sudo cp mail-server.acme.internal.pem /etc/dovecot/private/mail-server.crt -weight: 600;">sudo cp mail-server.acme.internal-key.pem /etc/dovecot/private/mail-server.key -weight: 600;">sudo chown root:dovecot /etc/dovecot/private/mail-server.key -weight: 600;">sudo chmod 640 /etc/dovecot/private/mail-server.key -weight: 600;">sudo cp mail-server.acme.internal.pem /etc/dovecot/private/mail-server.crt -weight: 600;">sudo cp mail-server.acme.internal-key.pem /etc/dovecot/private/mail-server.key -weight: 600;">sudo chown root:dovecot /etc/dovecot/private/mail-server.key -weight: 600;">sudo chmod 640 /etc/dovecot/private/mail-server.key -weight: 600;">sudo cp mail-server.acme.internal.pem /etc/dovecot/private/mail-server.crt -weight: 600;">sudo cp mail-server.acme.internal-key.pem /etc/dovecot/private/mail-server.key -weight: 600;">sudo chown root:dovecot /etc/dovecot/private/mail-server.key -weight: 600;">sudo chmod 640 /etc/dovecot/private/mail-server.key ssl_server_cert_file = /etc/dovecot/private/mail-server.crt ssl_server_key_file = /etc/dovecot/private/mail-server.key ssl_server_cert_file = /etc/dovecot/private/mail-server.crt ssl_server_key_file = /etc/dovecot/private/mail-server.key ssl_server_cert_file = /etc/dovecot/private/mail-server.crt ssl_server_key_file = /etc/dovecot/private/mail-server.key debian@mail-server:~/acme-certs$ cat /etc/dovecot/conf.d/10-auth.conf | grep "include auth" #!include auth-deny.conf.ext #!include auth-master.conf.ext #!include auth-oauth2.conf.ext #!include auth-system.conf.ext #!include auth-sql.conf.ext !include auth-ldap.conf.ext #!include auth-passwdfile.conf.ext #!include auth-static.conf.ext debian@mail-server:~/acme-certs$ cat /etc/dovecot/conf.d/10-auth.conf | grep "include auth" #!include auth-deny.conf.ext #!include auth-master.conf.ext #!include auth-oauth2.conf.ext #!include auth-system.conf.ext #!include auth-sql.conf.ext !include auth-ldap.conf.ext #!include auth-passwdfile.conf.ext #!include auth-static.conf.ext debian@mail-server:~/acme-certs$ cat /etc/dovecot/conf.d/10-auth.conf | grep "include auth" #!include auth-deny.conf.ext #!include auth-master.conf.ext #!include auth-oauth2.conf.ext #!include auth-system.conf.ext #!include auth-sql.conf.ext !include auth-ldap.conf.ext #!include auth-passwdfile.conf.ext #!include auth-static.conf.ext ldap_uris = ldaps://ldap-server.acme.internal ldap_auth_dn = cn=sssd,ou=-weight: 500;">service-accounts,dc=acme,dc=internal ldap_auth_dn_password = SSSDPass123 ldap_base = ou=users,dc=acme,dc=internal passdb ldap { ldap_filter = (&(objectClass=posixAccount)(uid=%{user})) ldap_bind = yes } userdb ldap { ldap_filter = (&(objectClass=posixAccount)(uid=%{user})) fields { uid = vmail gid = vmail home = /var/mail/vhosts/%{user | username} } } ldap_uris = ldaps://ldap-server.acme.internal ldap_auth_dn = cn=sssd,ou=-weight: 500;">service-accounts,dc=acme,dc=internal ldap_auth_dn_password = SSSDPass123 ldap_base = ou=users,dc=acme,dc=internal passdb ldap { ldap_filter = (&(objectClass=posixAccount)(uid=%{user})) ldap_bind = yes } userdb ldap { ldap_filter = (&(objectClass=posixAccount)(uid=%{user})) fields { uid = vmail gid = vmail home = /var/mail/vhosts/%{user | username} } } ldap_uris = ldaps://ldap-server.acme.internal ldap_auth_dn = cn=sssd,ou=-weight: 500;">service-accounts,dc=acme,dc=internal ldap_auth_dn_password = SSSDPass123 ldap_base = ou=users,dc=acme,dc=internal passdb ldap { ldap_filter = (&(objectClass=posixAccount)(uid=%{user})) ldap_bind = yes } userdb ldap { ldap_filter = (&(objectClass=posixAccount)(uid=%{user})) fields { uid = vmail gid = vmail home = /var/mail/vhosts/%{user | username} } } -weight: 600;">sudo adduser --system --no-create-home --group vmail -weight: 600;">sudo mkdir -p /var/mail/vhosts -weight: 600;">sudo chown -R vmail:vmail /var/mail/vhosts -weight: 600;">sudo chmod 770 /var/mail/vhosts -weight: 600;">sudo adduser --system --no-create-home --group vmail -weight: 600;">sudo mkdir -p /var/mail/vhosts -weight: 600;">sudo chown -R vmail:vmail /var/mail/vhosts -weight: 600;">sudo chmod 770 /var/mail/vhosts -weight: 600;">sudo adduser --system --no-create-home --group vmail -weight: 600;">sudo mkdir -p /var/mail/vhosts -weight: 600;">sudo chown -R vmail:vmail /var/mail/vhosts -weight: 600;">sudo chmod 770 /var/mail/vhosts mail_driver = maildir mail_home = /var/mail/vhosts/%{user | username} mail_path = %{home}/Maildir first_valid_uid = 100 mail_driver = maildir mail_home = /var/mail/vhosts/%{user | username} mail_path = %{home}/Maildir first_valid_uid = 100 mail_driver = maildir mail_home = /var/mail/vhosts/%{user | username} mail_path = %{home}/Maildir first_valid_uid = 100 -weight: 500;">service lmtp { unix_listener /var/spool/postfix/private/dovecot-lmtp { mode = 0600 user = postfix group = postfix } } -weight: 500;">service lmtp { unix_listener /var/spool/postfix/private/dovecot-lmtp { mode = 0600 user = postfix group = postfix } } -weight: 500;">service lmtp { unix_listener /var/spool/postfix/private/dovecot-lmtp { mode = 0600 user = postfix group = postfix } } -weight: 500;">service auth { unix_listener /var/spool/postfix/private/auth { mode = 0660 user = postfix group = postfix } } -weight: 500;">service auth { unix_listener /var/spool/postfix/private/auth { mode = 0660 user = postfix group = postfix } } -weight: 500;">service auth { unix_listener /var/spool/postfix/private/auth { mode = 0660 user = postfix group = postfix } } -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">restart dovecot -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">restart dovecot -weight: 600;">sudo -weight: 500;">systemctl -weight: 500;">restart dovecot debian@debian:~/acme-certs$ -weight: 500;">curl -v --url "smtp://mail-server.acme.internal:25" \ --mail-from "[email protected]" \ --mail-rcpt "[email protected]" \ --upload-file - <<EOF From: [email protected] To: [email protected] Subject: Test from jsmith Hello Alice, this is John. EOF debian@debian:~/acme-certs$ -weight: 500;">curl -v --url "smtp://mail-server.acme.internal:25" \ --mail-from "[email protected]" \ --mail-rcpt "[email protected]" \ --upload-file - <<EOF From: [email protected] To: [email protected] Subject: Test from jsmith Hello Alice, this is John. EOF debian@debian:~/acme-certs$ -weight: 500;">curl -v --url "smtp://mail-server.acme.internal:25" \ --mail-from "[email protected]" \ --mail-rcpt "[email protected]" \ --upload-file - <<EOF From: [email protected] To: [email protected] Subject: Test from jsmith Hello Alice, this is John. EOF 250 2.0.0 Ok: queued as 98B624018B 250 2.0.0 Ok: queued as 98B624018B 250 2.0.0 Ok: queued as 98B624018B debian@mail-server:~$ -weight: 600;">sudo ls /var/mail/vhosts/adoe/Maildir/new/ 1773903163.M693787P7845.mail-server.acme.internal,S=560,W=576 debian@mail-server:~$ -weight: 600;">sudo ls /var/mail/vhosts/adoe/Maildir/new/ 1773903163.M693787P7845.mail-server.acme.internal,S=560,W=576 debian@mail-server:~$ -weight: 600;">sudo ls /var/mail/vhosts/adoe/Maildir/new/ 1773903163.M693787P7845.mail-server.acme.internal,S=560,W=576 debian@debian:~/acme-certs$ -weight: 500;">curl -v --url "imaps://mail-server.acme.internal/INBOX" \ --user "adoe:Password456" debian@debian:~/acme-certs$ -weight: 500;">curl -v --url "imaps://mail-server.acme.internal/INBOX" \ --user "adoe:Password456" debian@debian:~/acme-certs$ -weight: 500;">curl -v --url "imaps://mail-server.acme.internal/INBOX" \ --user "adoe:Password456" * SSL certificate verify ok. * Connected to mail-server.acme.internal (192.168.57.7) port 993 < A002 OK Logged in < * LIST (\HasNoChildren) "." INBOX < A003 OK List completed (0.001 + 0.000 secs). * SSL certificate verify ok. * Connected to mail-server.acme.internal (192.168.57.7) port 993 < A002 OK Logged in < * LIST (\HasNoChildren) "." INBOX < A003 OK List completed (0.001 + 0.000 secs). * SSL certificate verify ok. * Connected to mail-server.acme.internal (192.168.57.7) port 993 < A002 OK Logged in < * LIST (\HasNoChildren) "." INBOX < A003 OK List completed (0.001 + 0.000 secs). - VM Intercommunication setup - Local TLS for HTTPS - OpenLDAP Home Lab