Skip to content

Debian 13 upgrade - #88

Open
AndrewSav wants to merge 8 commits into
mailserver2:masterfrom
AndrewSav:dovecot-2.4-debian-13
Open

Debian 13 upgrade#88
AndrewSav wants to merge 8 commits into
mailserver2:masterfrom
AndrewSav:dovecot-2.4-debian-13

Conversation

@AndrewSav

Copy link
Copy Markdown
Collaborator

Note

TLDR: fixes what Debian 13 update breaks. Test image: mailserver2/mailserver:debian13-test04. Confidence: MEDIUM. It's been running my server for last 24 hours okay, but given the breadth of fixed breakages, I won't be surprised if there are missed ones.

Important

Breaking change - LDAP configuration requires adjustment, see below and README.md I'm not running LDAP so I was not able to test drive this first hands

Description

Move the image to Debian 13 (trixie). Debian 13 brings Dovecot 2.4, postfix 3.10, Python 3.13, GnuPG 2.4, rspamd 4.1.5 and python3-watchdog 6.0.0, each of which breaks the current configuration in its own way, so the migration is split into one commit per breakage.

It incidentally resolves one open issue. ClamAV is installed from apt, so the base image dictates the engine version: bookworm tops out at 1.0.9, while trixie ships 1.4.3. Verified in the built image (clamd --version reports ClamAV 1.4.3, from clamav 1.4.3+dfsg-1).

Fixes #80

  • What is the current behavior (you can also link to an open issue here) ?

    The image is built on Debian 12 and cannot be built on Debian 13: Dovecot 2.4 refuses to start on a 2.3 configuration, postfix 3.10 warns on a deprecated parameter, zeyple dies on import under Python 3.13, rspamd 4.1.5 logs an error that fails the mail.err assertions, and the certificate watcher loops forever under watchdog 6.0.0.

  • What is the new behavior (if this is a feature change) ?

    The image builds and runs on Debian 13 with the full suite green.

Changes summary, expanded in the sections below:

  • Migrate the Dovecot configuration to 2.4 — Dovecot 2.4 refuses to start on a 2.3 configuration. It is rewritten in 2.4 syntax; quota usage reaches postfixadmin through quota_clone instead of the removed dict driver; quota-warning.sh no longer bounces its warnings; service auth gets a client_limit sized to the process limits, with the open-files limit raised to back it.
  • Report a failure to create the vmail user instead of hiding it — UID/GID 999 is systemd-journal on Debian 13, so a host using it silently got no vmail user, and Dovecot 2.4 will not start without one. The collision is now reported and fatal.
  • Adapt the postfix configuration to postfix 3.10smtpd_tls_dh1024_param_file is deprecated and warns on every postconf call, and Debian's packaging removes usr/lib/zoneinfo from the chroot. Both are dropped.
  • Fix zeyple on Debian 13 — Python 3.13 removed SafeConfigParser, so every message through the zeyple filter bounced, and GnuPG 2.4 no longer treats Key-Type: default as RSA. ConfigParser is imported and RSA requested explicitly.
  • Deny File/Path/Shm message inputs on the rspamd workers — rspamd 4.1.5 logs an error when those inputs are accepted on a non-loopback listener, which created mail.err. They are turned off; nothing used them.
  • Replace the retired default keyserver in encryption.sh — not an upgrade breakage: keys.gnupg.net had already been retired. Noticed during the migration, fixed here.
  • Ignore reads in the certificate watcher — python3-watchdog 6.0.0 reports reads, so the watcher's own reload retriggered it indefinitely. It now reacts only to changes, and the traefik fixtures mount a directory so the suite can exercise it.

The changes, commit by commit:

Migrate the Dovecot configuration to 2.4

Debian 13 ships Dovecot 2.4, which will not start on a 2.3 configuration: it requires dovecot_config_version as the first setting and refuses any setting it does not recognise. Every change below was verified against dovecot 2.4.1 from Debian 13 by rendering the templates with gucci and running doveconf over the result for the mysql, pgsql and ldap backends.

Mechanical renames:

mail_location                    -> mail_driver + mail_path
disable_plaintext_auth = yes     -> auth_allow_cleartext = no
ssl_cert / ssl_key               -> ssl_server_cert_file / _key_file
ssl_prefer_server_ciphers = yes  -> ssl_server_prefer_ciphers = server
service_count                    -> service_restart_request_count
sieve_quota_max_scripts/_storage -> sieve_quota_script_count/_storage_size
sieve_vacation_dont_check_recipient = yes -> sieve_vacation_check_recipient = no
passdb/userdb { driver = x }     -> passdb x { } / userdb x { }
passdb { pass = yes }            -> passdb { result_success = continue }

Restructured settings:

  • The plugin { } block is gone; its settings move to the top level.
  • The .conf.ext files for sql and ldap are gone: their settings are now written inline, so dovecot-sql.conf.ext, dovecot-dict-sql.conf.ext, dovecot-ldap.conf.ext and dovecot-ldap-master.conf.ext are replaced by conf.d/auth-sql.conf.ext and conf.d/auth-ldap.conf.ext.
  • sieve, sieve_default, sieve_after, sieve_dir and sieve_global become sieve_script storages; imapsieve_mailboxN_* become mailbox and imapsieve_from blocks.
  • zlib is replaced by mail_compress; imap_zlib no longer exists, as IMAP COMPRESS is handled by the core.
  • %-variables use the new filter syntax, e.g. %d -> %{user | domain}.

Behaviour changes worth noting:

  • 2.4 removed the dict quota driver, which is how usage reached the postfixadmin quota2 table. Quota is now tracked by the count driver and mirrored into quota2 by quota_clone through dict_server, so postfixadmin keeps reporting usage. Existing quota2 rows are recomputed as mailboxes are accessed.
  • quota_grace no longer accepts a percentage, so the former 10% becomes dovecot's default 10M.
  • verbose_ssl is gone; DEBUG_MODE now enables log_debug = category=ssl instead, and the assertions follow.

LDAP deployments need their LDAP_DOVECOT_*_ATTRS and _FILTER values converted to 2.4 syntax, as those are passed through verbatim. The ATTRS variables stay comma separated and are expanded into Dovecot's fields { } block, referencing attributes as %{ldap:attribute}. README documents the lookup migration, and the test values are updated accordingly.

Further 2.4 breakages fixed here:

  • A limit written inside the quota filter outranks the quota_storage_size the userdb returns per user, so every mailbox was pinned to the 5G default and doveadm quota get reported 5242880 for everyone. Declaring the default outside the filter keeps it as the fallback for users the userdb gives no quota for — the role quota_rule = *:storage=5GB played in 2.3 — while letting the per-user value win.
  • With LDAP_BIND=true dovecot binds as the user and never needs the password hash, and OpenLDAP does not return userPassword to the search. 2.3 ignored the missing attribute; 2.4 expands passdb fields after the lookup and logs No such attribute 'userpassword' on every login. Authentication still succeeded, but the noise fails the "mail.err does not exist" assertions, so the password mapping is dropped from the test values and documented as needed only when not binding as the user.
  • DEBUG_MODE uncommented every line of conf.d/10-logging.conf with sed 's/^#//g', so any explanatory comment became a bare word on its own line and dovecot refused to start — which then took postfix's submission service down with "no SASL authentication mechanisms". Match #setting = instead of any comment.
  • Debian's own conf.d/20-managesieve.conf now enables the sieve protocol unconditionally (protocols { sieve = yes }), where the 2.3 packaging left that line commented out. conf.d is included after the protocols line in dovecot.conf, so DISABLE_SIEVE could no longer close port 4190. Ship our own copy with just the service definitions.
  • Debian no longer patches the default login_greeting, so it is "Dovecot ready." rather than "Dovecot (Debian) ready.".
  • quota-warning.sh passed a 2.3-style -o plugin/quota=dict:User quota::noenforcing:proxy::sqlquota to dovecot-lda so the warning could be delivered without enforcing the quota. 2.4 dropped both the plugin/ prefix and the dict driver, and dovecot-lda ignores an unknown -o key silently, so enforcement stayed on and every warning to a mailbox at or over its limit bounced: nobody was ever told. It is now -o quota_enforce=no.
  • service auth and service anvil get an explicit client_limit derived from DOVECOT_MAX_PROCESS. 2.4 checks it against the sum of the login services' process_limit and prints an advisory on every config parse when it is short. That advisory lands in mail.err whenever the quota-warning script runs, because dovecot logs a script's stderr at error level, and with DOVECOT_MAX_PROCESS = nproc * 500 the default of 1000 was short on any host with three or more cores. The larger limit also has to be backed by open files: containers commonly inherit a soft limit of 1024 and Dovecot does not raise it per process (auth was measured at 1024 while configured for 4500), so run.sh now raises the soft limit to the hard limit before starting s6. If the hard limit is low as well, Dovecot warns at startup; the README says to set ulimits: nofile on the service in that case.

Report a failure to create the vmail user instead of hiding it

groupadd and useradd had their output sent to /dev/null, so a collision on VMAILUID/VMAILGID left the container running without a vmail user. Under Dovecot 2.3 that was survivable; 2.4 refuses to start at all (service(auth) User doesn't exist: vmail), and the visible symptom is postfix failing with "no SASL authentication mechanisms" because dovecot never created the auth socket — several steps removed from the cause.

Debian 13 makes this reachable: UID/GID 999 is systemd-journal there, where Debian 12 left it free, so a host user with GID 999 now collides.

Guard each command with a getent check so restarts stay quiet, and on failure report the offending UID/GID and touch /etc/setup-error, which run.sh already treats as fatal.

Adapt the postfix configuration to postfix 3.10

Two changes Debian 13's postfix requires:

  • smtpd_tls_dh1024_param_file is deprecated, and postfix 3.10 warns "support for parameter ... will be removed; instead, do not specify (leave at default)" on every postconf invocation. The warning goes to stderr, which bats folds into the assertion output, so it broke every postconf -h test. Postfix has used its own built-in FFDHE2048 group since 3.7, so the parameter and the ffdhe2048.pem it pointed at are both dropped.
  • usr/lib/zoneinfo is no longer created in the chroot. Debian's configure-instance.sh deletes it on startup from postfix 3.9.1-4 on ("unused"), so creating it achieved nothing and the assertion that it exists could never hold.

Fix zeyple on Debian 13

Two unrelated breakages from the newer userspace:

  • zeyple.py imported SafeConfigParser, which Python 3.12 removed (it had been a deprecated alias of ConfigParser since Python 3). Debian 13 ships Python 3.13, so the script died on import and every message routed through the zeyple content filter bounced with "Command died with status 1". Import ConfigParser instead.
  • GnuPG 2.4 fails the TESTING-only key generation with "key generation failed: Unknown elliptic curve": Key-Type: default no longer means RSA, so the accompanying Key-Length is read as a curve name. The empty keyring then failed the tests that look for the John Doe test key, and no mail could be encrypted. Ask for RSA explicitly — what default used to resolve to — at 2048 bits rather than the previous 1024.

Deny File/Path/Shm message inputs on the rspamd workers

rspamd 4.1.5 warns when a worker that accepts privileged File/Path/Shm message source inputs listens on a non-loopback TCP socket: any client able to reach the port can make rspamd read arbitrary files readable by the rspamd user and hand back their content. Both our proxy (11332) and controller (11334) bind *, so both are logged, and because the listener is not loopback the message is logged at error level and lands in /var/log/mail.err. That fails the "mail.err does not exist" test in every suite.

Nothing in the image uses those inputs: postfix reaches the proxy over milter and rspamc streams the message on stdin, so turning them off is not a functional change. rspamd will default this to false in the next major release anyway.

The warning does not exist in 4.1.0, so this only surfaces once the base image carries 4.1.5.

Replace the retired default keyserver in encryption.sh

encryption.sh defaulted to hkp://keys.gnupg.net, which was retired along with the SKS pool, so import-key and import-all-keys failed unless a keyserver was passed explicitly. The default is now hkps://keys.openpgp.org, which is also dirmngr's own default in GnuPG 2.4, and the README examples no longer cite pgp.mit.edu, retired as well. Unrelated to Debian 13; it is here because the migration is when it was noticed.

Ignore reads in the certificate watcher

watcher.py reacted to every inotify event in the watched certificate directory. Debian 13's python3-watchdog 6.0.0 also delivers events for reads (FileOpenedEvent, added in watchdog 2.3.0, and FileClosedNoWriteEvent, added in 5.0.0); Debian 12's 2.2.1 has neither, so this is new with the base image. The reload the watcher triggers reads those same files (jq over acme.json, cp -RLT of the letsencrypt live directory), so one genuine write, or a plain read such as traefik loading its store at startup, started an endless cycle: one certs_helper.sh reload every 3-4 s, about 1600 log lines a minute and half a core, observed on a live host and reproduced in the suite. Postfix and dovecot were not signalled, because update_certs stops at "Live Certificates match" when nothing changed; the cost is CPU and log volume, and restarting the container clears it until the next read. The handler now reacts only to created, modified, moved, deleted and closed (after write), which behaves the same on both watchdog versions.

The suite could not see this because the traefik fixtures bind-mounted acme.json as a single file, and inotify delivers a file's events to its parent directory on the host side of such a mount, so a watch on the container's /etc/letsencrypt/acme saw nothing at all. Production mounts the directory; the fixtures now do too (test/share/traefik/acme.vN/acme.json).

Build against the Debian 13 overlay

FROM mailserver2/debian-mail-overlay:1.1.0, the released Debian 13 (trixie) overlay, replacing 1.0.20 on Debian 12. Everything above is what that base change required.

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

Breaking for LDAP deployments only: LDAP_DOVECOT_*_ATTRS and LDAP_DOVECOT_*_FILTER are passed through verbatim and must be converted to Dovecot 2.4 syntax. See the migration section in the README.

Status

  • Ready

The overlay it builds on, mailserver2/debian-mail-overlay:1.1.0, is published, and the test changes this depends on (#86, #87) are on master, so there is nothing left to wait for.

Todo List

  • Implementation
  • Tests
  • Documentation
  • Build against the released overlay (1.1.0)

How has this been tested ?

The full bats suite, one suite at a time, against an image built on mailserver2/debian-mail-overlay:1.1.0: git submodule update --init --recursive, make build, then make default, make reverse, make ldap, make ldap2, make sieve, make ecdsa, make traefik_acmev1, make traefik_acmev2. Not as root: the Makefile passes id -u/id -g in as the vmail user and Dovecot refuses uid 0.

A test image built from this branch on overlay 1.1.0 is on Docker Hub as mailserver2/mailserver:debian13-test04 (digest sha256:db523ba714badd982768daca0b8045f38d6ed102ca8265776cd06f3840efb906), for trying it out without building; the results below were produced on it. It is a test tag, not a release, and will not be updated.

New tests

default (mysql):

  • quota usage is mirrored into quota2 — after the fixtures deliver, quota2.bytes and quota2.messages for john.doe are non-zero; the seed rows start at 0. Covers quota_clone standing in for the removed dict driver.
  • quota grace admits the message that crosses the limit — a message larger than tiny.quota's 100 KB quota is accepted under quota_storage_grace; doveadm quota get reports usage over 100%.
  • quota warning reaches an over-quota mailboxdoveadm search finds the "Mailbox quota warning" mail in that mailbox, i.e. quota-warning.sh delivers with enforcement off. Fails on the unfixed script.
  • mail to an over-quota mailbox is rejected — the next message to tiny.quota is refused at LMTP; mail.log carries the quota_exceeded_message for it.
  • delivered mail is compressed on disk — every file in john.doe's maildir is gzip, so mail_compress is in effect.
  • dovecot does not warn about the open files limit — no fd limit (ulimit -n) is lower than required line in mail.warn/mail.log; present before the run.sh change, and the only test that reads mail.warn.

traefik_acmev1, traefik_acmev2 and reverse:

  • one write to a watched certificate causes exactly one reload — the fixture rewrites acme.json (traefik) or fullchain.pem (reverse, letsencrypt mode) in place with identical bytes; the test asserts exactly one "Updating SSL certificates and reloading" and one "Live Certificates match" in docker logs. On the unfixed image the same fixture produced six cycles in about 18 s and this test alone failed.

reverse (pgsql):

  • quota usage is mirrored into quota2 — the same assertion over the pgsql dict backend.

Fixtures behind them: a tiny.quota@domain.tld mailbox with a 100 KB quota in the mariadb seed, and two extra messages at the end of fixtures_default — a filler larger than the quota, then one more that has to be refused. The traefik acme fixtures are mounted as directories, and fixtures_traefik_acmevN and the end of fixtures_reverse perform the in-place rewrite once postfix, dovecot and the watcher are up.

Updated expectations

  • rspamd Messages scanned 7 → 9 and Messages with action no action 5 → 7: the two extra fixture messages pass through rspamd before the quota refusal at LMTP. The accounting is in a comment above those tests.
  • Dovecot 2.4 renames: verbose_ssllog_debug, dict sqlquotadict_server, dovecot-sql.conf.extconf.d/auth-sql.conf.ext; login_greeting is "Dovecot ready." now that Debian no longer patches it.
  • The usr/lib/zoneinfo chroot assertion is dropped: postfix 3.10's Debian packaging removes that directory at startup.

Results

  • default — 128 tests
  • reverse — 81 tests
  • ldap — 66 tests
  • ldap2 — 29 tests
  • sieve — 2 tests
  • ecdsa — 4 tests
  • traefik_acmev1 — 11 tests
  • traefik_acmev2 — 12 tests

333 tests, 0 failures. The Debian 12 base runs 323; the ten added are listed above and every other count is unchanged, so no fixture stopped being exercised.

The rendered Dovecot configuration was also checked with doveconf against dovecot 2.4.1 for the mysql, pgsql and ldap backends.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ClamAV engine in image is outdated (1.0.7 vs current 1.0.9)

1 participant