Skip to content

chore: update base image from Debian 12 to Debian 13#4536

Draft
georglauterbach wants to merge 24 commits into
masterfrom
debian-13
Draft

chore: update base image from Debian 12 to Debian 13#4536
georglauterbach wants to merge 24 commits into
masterfrom
debian-13

Conversation

@georglauterbach

@georglauterbach georglauterbach commented Aug 2, 2025

Copy link
Copy Markdown
Member

Description

Note

I am hiding comments from time to time to keep the PR history somewhat clean and understandable.

This PR updates our base image from Debian 12 to Debian 13. I have tried to keep the diff as small as possible and only perform necessary changes. Almost all major components are affected, though.

Closes #4447
Fixes #4466
Closes #4512

Type of change

  • Update
  • Breaking change
  • This change requires a documentation update

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (README.md or the documentation under docs/)
  • If necessary, I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have added information about changes made in this PR to CHANGELOG.md

@georglauterbach georglauterbach added this to the v16.0.0 milestone Aug 2, 2025
@georglauterbach georglauterbach self-assigned this Aug 2, 2025
@georglauterbach georglauterbach added area/scripts area/features area/documentation area/configuration (file) kind/update Update an existing feature, configuration file or the documentation labels Aug 2, 2025
@georglauterbach georglauterbach moved this to Implementation Phase in DMS Features & Tasks Aug 2, 2025
@georglauterbach georglauterbach marked this pull request as draft August 2, 2025 13:05
@georglauterbach georglauterbach changed the title draft: chore: Debian 13 chore: update to Debian 13 Aug 2, 2025
polarathene

This comment was marked as outdated.

@polarathene

This comment was marked as resolved.

@georglauterbach

This comment was marked as resolved.

@georglauterbach

This comment was marked as outdated.

@georglauterbach georglauterbach force-pushed the debian-13 branch 2 times, most recently from ae4efa5 to 73ca10c Compare August 17, 2025 09:56
@github-actions github-actions Bot added the meta/stale This issue / PR has become stale and will be closed if there is no further activity label Sep 8, 2025
@docker-mailserver docker-mailserver deleted a comment from github-actions Bot Sep 8, 2025
@polarathene polarathene added stale-bot/ignore Indicates that this issue / PR shall not be closed by our stale-checking CI and removed meta/stale This issue / PR has become stale and will be closed if there is no further activity labels Sep 8, 2025
georglauterbach added a commit that referenced this pull request Feb 8, 2026
ref: #4536 (comment)

Signed-off-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
Signed-off-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
Signed-off-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
Signed-off-by: Georg Lauterbach <44545919+georglauterbach@users.noreply.github.com>
@georglauterbach

Copy link
Copy Markdown
Member Author

Solving the LDAP issues is quite frustrating. If someone can help, please chime in!

@github-project-automation github-project-automation Bot moved this from Implementation Phase to Done in DMS Features & Tasks Apr 4, 2026
@georglauterbach georglauterbach moved this from Done to Implementation Phase in DMS Features & Tasks Apr 4, 2026
@polarathene

polarathene commented Apr 13, 2026

Copy link
Copy Markdown
Member

@polarathene I really need your help with TLS now. Please take a look at parallel set 2.

Solving the LDAP issues is quite frustrating. If someone can help, please chime in!

Sorry for the lack of response for so long.. I had some technical difficulties (as usual 😓), back on Github now. I'll build the image from this PR starting from tomorrow and sort out the remaining TLS + LDAP test failures, since those are areas I'm familiar enough with I should be able to get those tests passing and we'll finally have this upgrade resolved! 😁

Huge appreciation for all the work you've done thus far on the task! ❤️ Once I've sorted the remaining tests out I'll review the PR fully.


Regarding the TLS failure

At a glance these are all failing only on Port 25, we don't manage that list explicitly (we do have some rules to filter out cipher suites, but we do not customize the cipher list order like we do for other service ports).

We hard-code the expected list in the failing test file here:

# Port 25 has a different server order, and also includes ARIA, CCM, DHE+CHACHA20-POLY1305 cipher suites:
# RSA (Port 25):
CIPHER_LIST["intermediate-rsa_TLSv1_2_p25"]='"ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-GCM-SHA384 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-CHACHA20-POLY1305 DHE-RSA-AES256-CCM8 DHE-RSA-AES256-CCM ECDHE-ARIA256-GCM-SHA384 DHE-RSA-ARIA256-GCM-SHA384 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES256-SHA256 ARIA256-GCM-SHA384 ECDHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-CCM8 DHE-RSA-AES128-CCM ECDHE-ARIA128-GCM-SHA256 DHE-RSA-ARIA128-GCM-SHA256 ECDHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA256 ARIA128-GCM-SHA256"'
# ECDSA (Port 25):
CIPHER_LIST["intermediate-ecdsa_TLSv1_2_p25"]='"ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-CCM8 ECDHE-ECDSA-AES256-CCM ECDHE-ECDSA-ARIA256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES128-CCM8 ECDHE-ECDSA-AES128-CCM ECDHE-ECDSA-ARIA128-GCM-SHA256 ECDHE-ECDSA-AES128-SHA256"'
# ECDSA + RSA fallback, dual cert support (Port 25):
CIPHER_LIST["intermediate-ecdsa-rsa_TLSv1_2_p25"]='"ECDHE-ECDSA-AES256-GCM-SHA384 ECDHE-RSA-AES256-GCM-SHA384 DHE-RSA-AES256-GCM-SHA384 ECDHE-ECDSA-CHACHA20-POLY1305 ECDHE-RSA-CHACHA20-POLY1305 DHE-RSA-CHACHA20-POLY1305 ECDHE-ECDSA-AES256-CCM8 ECDHE-ECDSA-AES256-CCM DHE-RSA-AES256-CCM8 DHE-RSA-AES256-CCM ECDHE-ECDSA-ARIA256-GCM-SHA384 ECDHE-ARIA256-GCM-SHA384 DHE-RSA-ARIA256-GCM-SHA384 ECDHE-ECDSA-AES256-SHA384 ECDHE-RSA-AES256-SHA384 DHE-RSA-AES256-SHA256 ARIA256-GCM-SHA384 ECDHE-ECDSA-AES128-GCM-SHA256 ECDHE-RSA-AES128-GCM-SHA256 DHE-RSA-AES128-GCM-SHA256 ECDHE-ECDSA-AES128-CCM8 ECDHE-ECDSA-AES128-CCM DHE-RSA-AES128-CCM8 DHE-RSA-AES128-CCM ECDHE-ECDSA-ARIA128-GCM-SHA256 ECDHE-ARIA128-GCM-SHA256 DHE-RSA-ARIA128-GCM-SHA256 ECDHE-ECDSA-AES128-SHA256 ECDHE-RSA-AES128-SHA256 DHE-RSA-AES128-SHA256 ARIA128-GCM-SHA256"'

Exclusions are handled here:

smtpd_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtp_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtpd_tls_mandatory_ciphers = high
smtpd_tls_mandatory_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1
smtpd_tls_exclude_ciphers = aNULL, SEED, CAMELLIA, RSA+AES, SHA1

The expected vs actual results appear to only differ by sorting, so presumably Postfix adjusted this in one of it's newer releases since 3.6.

  • Notably *-CCM8 was all demoted to the end of the list (this particular variant is better for some embedded clients IIRC, which if we only offer AEAD ciphers we could allow the client to choose it's preferred cipher-suite to negotiate, but for the port 25 list I don't think we do).
  • It should be okay to trust Postfix here if that's the case (nothing about the changed order is too concerning), so we can just adjust the referenced .bats lines to pass those tests.

EDIT: Only *-CCM8 suites were shifted to the end of the server-side preference list, their priority amongst themselves otherwise remains intact. CCM8 is for embedded, and has reduced security due to the Message Authentication Code (MAC) tag length (8 byte / 64-bit vs 16 byte / 128-bit). Change was due to OpenSSL policy config in Debian 13 (which Postfix inherited).

LDAP isn't as obvious at a glance and I'll troubleshoot that after some rest 👍


Progress

UPDATE: I've resolved all LDAP issues too, didn't take long but energy is too low to publish details (I managed to write-up about 70% of it 😅), I'll wrap that up tomorrow and follow-up with applying the necessary changes.

  • Resolve LDAP + TLS failures
  • Review PR in full
  • Ensure changelog updated
  • Ensure docs also have any applicable migration changes applied where necessary

@polarathene

polarathene commented Apr 13, 2026

Copy link
Copy Markdown
Member

Likely worth adding Changelog entries about for v16:

Fail2Ban 1.1.0 is a bit outdated and until a 1.1.1 release is published, there is an incompatibility with Dovecot 2.4 (which our tests supposedly didn't catch):

The Rspamd 4.0 release has breaking changes and is an implicit bump in the debian repo we pull it from (something that I recall the rspamd author said wouldn't happen, when they refused assistance to setup publishing versioned packages that we could have pinned).

@polarathene

Copy link
Copy Markdown
Member

Posting this a little early to avoid risk of data loss again on my end 😓

LDAP test failures

Failure 1 - SASLAuthd

LDAP test failure had simplest reproduction with this command to test auth:

$ testsaslauthd -u some.user -p secret
0: NO "authentication failed"
Related LDAP config
$ doveconf

# ...

ldap_auth_dn = cn=admin,dc=example,dc=test
ldap_auth_dn_password = # hidden, use -P to show it
ldap_base = ou=users,dc=example,dc=test
ldap_uris = ldap://ldap.example.test

# ...

passdb ldap {
  driver = ldap
  fields {
    auth_bind = no
    default_pass_scheme = SSHA
    ldap_version = 3
    pass_attrs = uniqueIdentifier=user,userPassword=password
    tls = no
    user_attrs = mailHomeDirectory=home,mailUidNumber=uid,mailGidNumber=gid,mailStorageDirectory=mail
  }
  filter = (&(userID=%{user | username})(objectClass=PostfixBookMailAccount))
}

userdb ldap {
  driver = ldap
  fields {
    auth_bind = no
    default_pass_scheme = SSHA
    ldap_version = 3
    pass_attrs = uniqueIdentifier=user,userPassword=password
    tls = no
    user_attrs = mailHomeDirectory=home,mailUidNumber=uid,mailGidNumber=gid,mailStorageDirectory=mail
  }
  filter = (&(userID=%{user | username})(objectClass=PostfixBookMailAccount))
}
$ cat /etc/saslauthd.conf

ldap_servers: ldap://ldap.example.test
ldap_auth_method: bind
ldap_bind_dn: cn=admin,dc=example,dc=test
ldap_bind_pw: admin
ldap_search_base: ou=users,dc=example,dc=test
ldap_filter: (&(userID=%U)(mailEnabled=TRUE))
ldap_start_tls: no
ldap_tls_check_peer: no
ldap_referrals: yes
log_level: 10

Failures were due to the PR likely having a find/replace operation applied carelessly for %u => %{user | username} (Dovecot 2.3 to 2.4 breaking change migration), as that replacement was also applied to %U (see upstream docs for this syntax token) that not only was a different casing, but also for an entirely different program (SASLAuthD).

local SASLAUTHD_QUERY='(&(userID=%U)(mailEnabled=TRUE))'


Failure 2 - ENV config override

Meanwhile, this test case was failing because the override was no longer being applied (thus the "actual" output mismatch reported from the failure was the original config unmodified):

uris = ldap://mail.example.com


Failure 3 - LDAP UserDB + PassDB

The test-case dovecot: ldap mail delivery works was failing due to botched migration of Dovecot LDAP config, there was copy/paste of content from target/dovecot/dovecot-ldap.conf.ext as Dovecot 2.4 no longer delegates args to separate .conf.ext files the LDAP settings must be adapted accordingly (as documented upstream).

  • default_pass_scheme = SSHA remains valid, but is only necessary if the password itself lacks a password scheme prefix. We do this in our examples, unclear about actual users own LDAP setups, so we should probably keep that for now given the upstream default is CRYPT.
  • ldap_version = 3 is the default value already.
  • tls = no maps to ldap_starttls = no (default).
    • This setting is only relevant for connecting to LDAP over port 389 instead of implicit TLS (preferred) over port 636 (which is instead handled via ldap_uris with an FQDN prefixed with ldaps://), as detailed here in upstream docs.
    • Port 389 can still use ldap:// scheme with ldap_uris and ldap_starttls will control if StartTLS is negotiated to upgrade the connection to be secured over TLS.
  • auth_bind = no maps to passdb_ldap_bind = no (default).
  • passdb_filter and userdb_filter are both treated in Dovecot 2.4 as a filter field in their respective passdb/userdb entries.
    • In Dovecot 2.4, we can however also add these external of the passdb/userdb objects and use the equivalent global name (that references that object type with the assigned driver name as a prefix to a field), such that passdb_ldap_filter for example is also valid, and thus easier for us to target via scripts? (this Dovecot 2.4 settings feature is documented early in the migration guide)
    • Your workaround was to insert invalid field names for scripts to target and then later use sed on the config to correct it. Going with the global setting name instead seems preferable?
  • Likewise user_attrs/passdb_attrs was changed to fields (thus global setting names of userdb_ldap_fields / passdb_ldap_fields). This is notably where misconfiguration occurred, preventing successful authentication/lookup of LDAP users. EDIT: Actually this might still be supported via userdb_import but I don't see an equivalent for pass_attrs.

We'd instead want something like this:

# Replaced by ldap.sh
ldap_uris             = REPLACED
ldap_base             = REPLACED
ldap_auth_dn          = REPLACED
ldap_auth_dn_password = REPLACED

passdb ldap {
  driver = ldap
  filter = (&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%{user | username}))

  fields {
    default_pass_scheme = SSHA
    user = %{ldap:uniqueIdentifier}
    password = %{ldap:userPassword}
  }
}

userdb ldap {
  driver = ldap
  filter = (&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%{user | username}))

  fields {
    uid = %{ldap:mailUidNumber}
    gid = %{ldap:mailGidNumber}
    home = %{ldap:mailHomeDirectory}
    mail_path = %{ldap:mailStorageDirectory}
  }
}

@polarathene

Copy link
Copy Markdown
Member

This comment is just additional notes/insights for reference.

Failure 3 - Dovecot LDAP PassDB (no test coverage)

With our mail_with_ldap.bats test container we can do the following (applicable to earlier DMS releases):

# Requires `ENABLE_SASLAUTHD=1`:
# (this is an alternative SASL provider to Dovecot, only relevant to Postfix auth)
$ testsaslauthd -u some.user -p secret
0: OK "Success."

# Regardless of `ENABLE_SASLAUTHD`, the following tests Dovecot's `passdb` auth:
# (fails because our LDAP user has no `uniqueIdentifier` attribute to query accounts against)
$ doveadm auth test "some.other.user" "secret"
Error: cmd auth test: user some.other.user: internal auth failure
extra fields:
  user=some.other.user
  code=temp_fail

The uniqueIdentifier attribute as a default is configured in DMS for each SASL provider at these locations (DMS v15.1 / edge):

ldap_filter: ${SASLAUTHD_LDAP_FILTER:=(&(uniqueIdentifier=%u)(mailEnabled=TRUE))}

pass_attrs = uniqueIdentifier=user,userPassword=password
pass_filter = (&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))

And in our mail_with_ldap.bats test file, we override those SASLAuthd and Dovecot LDAP pass filters to instead match on the LDAP accounts userID attribute configured as here:

local SASLAUTHD_QUERY='(&(userID=%U)(mailEnabled=TRUE))'

local DOVECOT_QUERY_PASS='(&(userID=%n)(objectClass=PostfixBookMailAccount))'

There's no modification to Dovecot's pass_attrs (but we do support and document that), as such Dovecot passdb can find a result successfully with it's LDAP query, but we expected that response to return a uniqueIdentifier attribute to map to the Dovecot field user value... hence the failure.

NOTE:

Example:

# `user = %{ldap:mail}` changes the `user` field to the full mail address:
# `original_user` is included in the returned fields to preserve the login username
$ doveadm auth test "some.other.user" "secret"
passdb: some.other.user auth succeeded
extra fields:
  user=some.other.user@localhost.otherdomain
  default_pass_scheme=SSHA
  original_user=some.other.user


# Since the `filter` takes the auth credential input `user` and then truncates any `@<domain-part>`
# via the syntax `%{user | username}` (Dovecot 2.4) / `%n` (Dovecot 2.3),
# we can also authenticate with the mail address as `userID` in both cases is matched to `some.other.user`.
# 
# In this scenario the login username already matches the LDAP `mail` attribute,
# thus no `original_user` field is returned.
$ doveadm auth test "some.other.user@localhost.otherdomain" "secret"
passdb: some.other.user@localhost.otherdomain auth succeeded
extra fields:
  user=some.other.user@localhost.otherdomain
  default_pass_scheme=SSHA

As mentioned earlier, PassDB will return user which in an auth flow for something like a MUA client accessing a mailbox over IMAP will be used for the UserDB lookup AFAIK to proceed. This can be tested with the following:

Failure:

# `user=%{ldap:mailAlias}` (postmaster@localhost.otherdomain):
# That `user` field is then queried to the UserDB after successful auth
# and no matching `userID` was found, so the login fails.
$ doveadm auth login "some.other.user" "secret"

passdb: some.other.user auth succeeded
extra fields:
  user=postmaster@localhost.otherdomain
  default_pass_scheme=SSHA
  original_user=some.other.user
Error: auth-master: login: request [3707109377]: Login auth request failed: Authenticated user not found from userdb, auth lookup id=3707109377 (auth connected 0 msecs ago, request took 0 msecs, client-pid=1733 client-id=1)
Error: cmd auth login: user some.other.user: userdb lookup failed: Internal error occurred. Refer to server log for more information.

Success:

# With `user=%{ldap:mail}` / `user=%{ldap:userID}` or as below no `user` field set:
# Any of these would succeed as their `user` value will match an LDAP account by `userID`.
$ doveadm auth login "some.other.user" "secret"

passdb: some.other.user auth succeeded
extra fields:
  user=some.other.user
userdb user: some.other.user
userdb extra fields:
  uid=5000
  gid=5000
  home=/var/mail/localhost.localdomain/some.other.user/
  mail_path=/var/mail/localhost.localdomain/some.other.user/
  auth_mech=PLAIN

NOTE: IIRC, there's another caveat with auth configuration (unrelated to the changes in this PR) that affected OAuth if a username / address wasn't properly canonicalized.

@polarathene polarathene left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll apply these myself. Submitting review in it's current state to avoid loss/corruption.

I don't know what Github devs are up to but the review process is presently iffy (or my system/browser is somehow affecting websites functionality).

  • Suggestions aren't appearing to have relevant - lines.
  • I added a review comment and it was not visible in the web UI, but appears to be applied so hopefully submitting what I have presently will preserve that.
  • I attempted to edit a review comment I did have added, but it's content lost all markdown syntax and other formatting issues which prevents me easily making a small change 😕

This review covers the bulk of LDAP feedback related to the current test failures. There's still other changes for LDAP by this PR that need to be reviewed/modified however, along with other portions of the PR where things look off/incorrect 😅

Comment thread test/tests/serial/mail_with_ldap.bats Outdated
# required to match the mail accounts actual `mail` attribute (nor the local-part), they are distinct.
# TODO: Docs should better cover this difference, as it does confuse some users of DMS (and past contributors to our tests..).
local SASLAUTHD_QUERY='(&(userID=%U)(mailEnabled=TRUE))'
local SASLAUTHD_QUERY='(&(userID=%{user | username})(mailEnabled=TRUE))'

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test case failure:

 ✗ [LDAP] saslauthd: sasl ldap authentication works [600154]
   (from function `assert_success' in file test/test_helper/bats-assert/src/assert_success.bash, line 42,
    in test file test/tests/serial/mail_with_ldap.bats, line 309)
     `assert_success' failed

   -- command failed --
   status : 255
   output : 0: NO "authentication failed"
   --

Fix the SASLAuthD test by reverting this mistaken find/replace, it is not the Dovecot token %u:

Suggested change
local SASLAUTHD_QUERY='(&(userID=%{user | username})(mailEnabled=TRUE))'
local SASLAUTHD_QUERY='(&(userID=%U)(mailEnabled=TRUE))'

@polarathene polarathene Apr 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolving that test case then resolves the following other test case failures:

  • ✗ [LDAP] saslauthd: ldap smtp authentication
  • ✗ [LDAP] spoofing (with LDAP): rejects sender forging
  • ✗ [LDAP] spoofing (with LDAP): accepts sending as alias

These are Postfix related (delegating auth through to Dovecot).

If the mail_with_ldap.bats DMS container did not set the ENV ENABLE_SASLAUTHD=1, instead of SASLAuthd these separate tests would then have relied upon Dovecot SASL for LDAP auth queries (default for DMS).

ENABLE_SASLAUTHD=0 (Delegating to Dovecot) would also have failed too, as the related LDAP config for Dovecot SASL was not migrated to 2.4 correctly.

Comment on lines 266 to 271
local LDAP_SETTINGS_DOVECOT=(
"uris = ldap://${FQDN_LDAP}"
'tls = no'
'base = ou=users,dc=example,dc=test'
'dn = cn=admin,dc=example,dc=test'
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test case failure:

 ✗ [LDAP] dovecot: ldap config overwrites success [218]
   (from function `assert_output' in file test/test_helper/bats-assert/src/assert_output.bash, line 194,
    in test file test/tests/serial/mail_with_ldap.bats, line 275)
     `assert_output "${LDAP_SETTING}"' failed

   -- output differs --
   expected : uris = ldap://ldap.example.test
   actual   : uris                = ldap://mail.example.com
   --

This test case is failing due to migration of settings from the config file dovecot-ldap.conf.ext (this file in Dovecot 2.4 is redundant and should be deleted) into the main LDAP auth config file auth-ldap.conf.ext.

  • I've hard-coded the URI value despite the available FQDN_LDAP variable since the domain itself is hard-coded in the other two values 🤷‍♂️
  • Dropped the tls = no as it is the upstream default anyway (along with our own supplied base config, which I think was only to support the ENV override features support), I don't think we need to check an override against that to the same value, unless we were setting it to yes as a default in our scripts somewhere (if so the setting was renamed from tls to ldap_starttls).
Suggested change
local LDAP_SETTINGS_DOVECOT=(
"ldap_uris = ldap://ldap.example.test" # ldap://${FQDN_LDAP}
'ldap_base = ou=users,dc=example,dc=test'
'ldap_auth_dn = cn=admin,dc=example,dc=test'
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Weirdly the Github suggestions feature isn't working that well lately 🤔 It's proposing to replace line 266 only, not the 266-270 range.

So this will need to be edited manually rather than applying suggestions that seem to append instead of replace multi-line content 😑

Comment thread test/tests/serial/mail_with_ldap.bats Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise correct the file to check so the test actually passes :)

Suggested change
_run_in_container grep "${LDAP_SETTING%=*}" /etc/dovecot/conf.d/auth-ldap.conf.ext

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Likewise with this single line suggestion replacement, the suggestion doesn't seem to acknowledge the actual content of line 274 to be replaced, treating it as a blank line 🤷‍♂️ (I don't know how Github manages to mess something like that up, perhaps they're trying to rely on AI assisted dev/agents via Copilot? 🤔)

Comment on lines +13 to 39
passdb ldap {
driver = ldap
mechanisms = plain login
user_filter = (&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))

# Path for LDAP configuration file, see example-config/dovecot-ldap.conf.ext
args = /etc/dovecot/dovecot-ldap.conf.ext
fields {
default_pass_scheme = SSHA
tls = no
ldap_version = 3
pass_attrs = uniqueIdentifier=user,userPassword=password
user_attrs = mailHomeDirectory=home,mailUidNumber=uid,mailGidNumber=gid,mailStorageDirectory=mail
auth_bind = no
}
}

userdb {
userdb ldap {
driver = ldap
args = /etc/dovecot/dovecot-ldap.conf.ext
pass_filter = (&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))

# Default fields can be used to specify defaults that LDAP may override
#default_fields = home=/home/virtual/%u
fields {
default_pass_scheme = SSHA
tls = no
ldap_version = 3
pass_attrs = uniqueIdentifier=user,userPassword=password
user_attrs = mailHomeDirectory=home,mailUidNumber=uid,mailGidNumber=gid,mailStorageDirectory=mail
auth_bind = no
}
}

@polarathene polarathene Apr 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test case failure:

 ✗ [LDAP] dovecot: ldap mail delivery works
   (from function `_repeat_until_success_or_timeout' in file test/helper/common.bash, line 196,
    from function `_repeat_in_container_until_success_or_timeout' in file test/helper/common.bash, line 158,
    from function `_wait_for_empty_mail_queue_in_container' in file test/helper/common.bash, line 333,
    from function `_should_successfully_deliver_mail_to' in file test/tests/serial/mail_with_ldap.bats, line 439,
    in test file test/tests/serial/mail_with_ldap.bats, line 257)
     `_should_successfully_deliver_mail_to "some.user@${FQDN_LOCALHOST_A}" "/var/mail/${FQDN_LOCALHOST_A}/some.user/new/"' failed
   Timed out on command: _exec_in_container /bin/bash -c [[ $(mailq) == "Mail queue is empty" ]]

NOTE: Due to ENABLE_SASLAUTHD=1 there's only one test failure to resolve here, otherwise it'd cover 3 more test cases that are from Postfix delegating LDAP auth to either Dovecot or SASLAuthd.


This failure was due to Dovecot not being configured to query LDAP properly to do the following:

In our LDAP UserDB the LDAP attribute mailStorageDirectory was previously configured with a value for mail_location/mail (Dovecot 2.3) which allowed for <mail_driver>:<mail_path>. This breaking change with Dovecot 2.4 (mandatory splitting the components to separate settings) requires users to update this on the LDAP side. In our tests .ldif files for example, this also caused a test failure that tests for a customized storage path (to use an unrelated domain-part localhost.localdomain for storage instead of conventional localhost.otherdomain to match the mail address domain-part).

Corrected form (with added todo task to resolve after this PR, so that the change itself is not lost in the noise of a massive changeset that is primarily focused only changes to support an image upgrade):

Suggested change
passdb ldap {
driver = ldap
mechanisms = plain login
user_filter = (&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))
# Path for LDAP configuration file, see example-config/dovecot-ldap.conf.ext
args = /etc/dovecot/dovecot-ldap.conf.ext
fields {
default_pass_scheme = SSHA
tls = no
ldap_version = 3
pass_attrs = uniqueIdentifier=user,userPassword=password
user_attrs = mailHomeDirectory=home,mailUidNumber=uid,mailGidNumber=gid,mailStorageDirectory=mail
auth_bind = no
}
}
userdb {
userdb ldap {
driver = ldap
args = /etc/dovecot/dovecot-ldap.conf.ext
pass_filter = (&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%n))
# Default fields can be used to specify defaults that LDAP may override
#default_fields = home=/home/virtual/%u
fields {
default_pass_scheme = SSHA
tls = no
ldap_version = 3
pass_attrs = uniqueIdentifier=user,userPassword=password
user_attrs = mailHomeDirectory=home,mailUidNumber=uid,mailGidNumber=gid,mailStorageDirectory=mail
auth_bind = no
}
}
# TODO: Add breaking change after migration to Dovecot 2.4 is completed.
# The `filter` for both passdb + userdb should be changed to (`uniqueIdentifier` => `userID`):
# (&(userID=%{user | username})(objectClass=PostfixBookMailAccount))
# This change will also remove the need for the modification via ENV in `mail_with_ldap.bats`
# Additionally requires documentation update on pages related to LDAP config.
passdb ldap {
driver = ldap
filter = (&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%{user | username}))
fields {
default_pass_scheme = SSHA
user = %{ldap:uniqueIdentifier}
password = %{ldap:userPassword}
}
}
userdb ldap {
driver = ldap
filter = (&(objectClass=PostfixBookMailAccount)(uniqueIdentifier=%{user | username}))
fields {
uid = %{ldap:mailUidNumber}
gid = %{ldap:mailGidNumber}
home = %{ldap:mailHomeDirectory}
mail_path = %{ldap:mailStorageDirectory}
}
}

NOTE: We could include mail_driver = maildir in the UserDB field, but that'd have to be static config I think? I don't think that the schema is setup for customizing that. We also have globals set in 10-mail.conf (which this PR handles migration of mail_location to mail_driver + mail_path), when these fields aren't returned from the LDAP response, Dovecot should fallback to the globals. If someone actually needs per-account configuration of mail_driver then they can raise an issue for support of that.

@polarathene polarathene Apr 25, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: The changelog will need to notify our LDAP users about this breaking change with mail => mail_path, in addition to the ENV name changes for affecting modifications to these LDAP config settings.

Regarding the filter setting for both passdb and userdb:

  • We can override these by appending the namespaced setting (eg: passdb_<name>_filter / passdb_ldap_filter in this case).
  • I'm not too fond of the current sed workaround change proposed in target/scripts/startup/setup.d/ldap.sh relying on invalid names in the default config we provide here, appending a modified file with namespaced settings will work fine instead.
  • For filter specifically we could already just append these at the end within the config file instead, and tests would pass without adding append logic. That said, I'm tempted towards preferring a user to supply a config override themselves instead of relying on ENV which we already support via dovecot.cf and with Dovecot 2.4 such overrides are much easier (would technically avoid this concern?). However, we do provide a convenience via ENV that can be useful with common service agnostic LDAP settings that get applied to each services config, and that's still kinda worthwhile.

PassDB in the test should have user = %{ldap:mail}. Without pass_attrs being a thing anymore, this will break our ENV related config support to do this. These can be edited with each settings individual long namespaced name however AFAIK, so support shouldn't be that difficult to cover. Similar is needed anyway for the filter field.

⚠️ mail_with_ldap.bats doesn't have coverage for Dovecot's LDAP PassDB config. Thus this issue won't appear visible, except for the failure from user = %{ldap:uniqueIdentifier} failing to have the expected attribute returned from LDAP. Easy fix here is to just remove the user field itself, but a proper test case using the relevant override support would be ideal.

Comment thread target/scripts/startup/setup.d/ldap.sh Outdated
Comment on lines +51 to +54
sed -i -E \
-e 's|user_filter| filter|' \
-e 's|pass_filter| filter|' \
/etc/dovecot/conf.d/auth-ldap.conf.ext

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a fan of this workaround to map ENV to the respective Dovecot filters, remove:

Suggested change
sed -i -E \
-e 's|user_filter| filter|' \
-e 's|pass_filter| filter|' \
/etc/dovecot/conf.d/auth-ldap.conf.ext

We can use namespaced settings userdb_ldap_filter + passdb_ldap_filter instead. The current ENV support can match that, or we can append the equivalent setting to override.

@polarathene

polarathene commented May 16, 2026

Copy link
Copy Markdown
Member

@georglauterbach no need to ping for review :) yes tests pass.. but there's still quite a bit of the PR itself that needs to be revised as I can see concerns there. I didn't have any spare energy to tackle that today.

Assuming my system or environment (power/internet) don't introduce interruptions again (I've had so many setbacks this year due to these problems), I'll be doing the full PR review tomorrow 👍

Due to the issues I've been having lately I'll probably reduce time spent on review feedback/discussion and push commits without detailed context until I'm happy with clicking approve. This PR has been delayed enough as it is :(

@github-actions

Copy link
Copy Markdown
Contributor

Documentation preview for this PR is ready! 🎉

Built with commit: 253e7b4

@polarathene

polarathene commented Jun 6, 2026

Copy link
Copy Markdown
Member

Right well... I've made a foolish mistake 😓

TL;DR: System crashed again.

  • Firefox somehow got quite memory hungry and pushed memory usage up to 99% exceeding the 90GB capacity for committed memory (system memory + disk pagefile).
  • In an attempt to try stay focused and keep memory usage down, I used an ephemeral session and made the poor decision to use the GH web IDE for pending changes that I didn't commit quickly. Once hitting OOM unexpectedly, uncommitted changes were lost.

Will continue with return to review again tomorrow.

Rant (collapsed due to verbosity)

Without a new system or taking time to better archive existing sessions of tabs in browsers and code editors, I used an ephemeral browser session for it's web editor and for the changes I had made in the Github web editor IDE, but hadn't committed directly to this branch I was working on, they've now been lost 😑

Windows 11 would report only 20GB of my 32GB RAM was allocated to the browser, but last night (ironically just as I was about to ensure I have an offline backup copy of everything..), instead of a full system crash it was just the browser session. RAM was reported at 95% and spiked to 99%, along with disk and about 25% CPU. When this happens after resources settle, I can usually continue, but the browser (Firefox) became unresponsive, and since it was an ephemeral session that isn't recoverable and the Github web IDE wasn't storing that session in a way that I could access my changes via other browsers, so I'm assuming it relied on browser storage feature locally that wouldn't persist in an private/ephemeral session.

I don't understand Windows memory management well. While 30/32GB RAM is allocated (of which 4GB is apparently compressed), the pagefile included the committed memory section defaults to approx 3x my system memory, and some how is at 80GB, presumably during the spike it hit 90GB and Firefox became unresponsive (yet continues to have the 20GB allocation reported in Task Manager, I haven't killed the program whilst trying to recover). I don't have anything else open, GPU VRAM shouldn't have been exhausted either, so the committed memory ceiling being reached is the only thing that I think explains the unresponsiveness and I assume that represents my system memory + pagefile (equivalent to swap on linux?). The browser AFAIK was unloading tabs when under memory pressure, I'm just rather confused why 90GB would be used by a browser 🤷‍♂️ (I miss Linux as the host OS)


Anyway... until I have time/finances to get a fresh system setup I will just need to be more careful and less trusting of the system (these failures happen too often this year 😞).

I'm trying to recollect roughly what I did, but some of it was quite a time sink that I can't justify repeating. I know there was concerns with postscreen (various issues addressed) and dovecot quotas (race condition with custom quota + delete, leading to two different failure events), and I had wrote some fixes to test helpers along with very useful docs and corrections on the postscreen concerns 😭

I was reckless and got distracted with other DMS activity, apologies :(

Archiving notes on what I have in the local git clone of this branch and terminal tabs. After that I'll reboot the system and take another shot at staying on track with the review of the PR.

@polarathene polarathene left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike the GH web IDE, pending review comments weren't lost. So I'll submit these before that becomes a possibility 😓

fields {
uid:default = docker
gid:default = docker
home:default = /var/mail/%{user | domain}/%{user}/home/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug (carried over from existing releases, introduced when Dovecot was originally added into DMS)

The %u should have been %n here in prior releases. For Dovecot 2.4 that would be %{user | username} to ensure only the local-part is used.

In practice this doesn't affect DMS as we always populate the home field in the /etc/dovecot/userdb lines we generate for file provisioned accounts, so this fallback would not be used. Still we should correct this in a follow-up PR.

passdb passdb-default {
driver = passwd-file
passwd_file_path = /etc/dovecot/userdb
auth_username_format = %{user}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Bug (carried over from existing releases, introduced when Dovecot was originally added into DMS)

auth_username_format defaults to this value but with a lowercase transform to normalize that we've avoided by this assignment.

The old Dovecot docs have a minimal guide that advised username_format=%u (Dovecot 2.3 equivalent of this line), but on another page instead chooses username_format=%n (just the local-part), however the default in Dovecot 2.3 is also %Lu (Dovecot 2.4 sets the equivalent value with %{user | lowercase}).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/update Update an existing feature, configuration file or the documentation priority/high stale-bot/ignore Indicates that this issue / PR shall not be closed by our stale-checking CI

Projects

Status: Implementation Phase

3 participants