Debian 13 upgrade - #88
Open
AndrewSav wants to merge 8 commits into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 --versionreportsClamAV 1.4.3, fromclamav 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.errassertions, 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:
quota_cloneinstead of the removed dict driver;quota-warning.shno longer bounces its warnings;service authgets aclient_limitsized to the process limits, with the open-files limit raised to back it.smtpd_tls_dh1024_param_fileis deprecated and warns on everypostconfcall, and Debian's packaging removesusr/lib/zoneinfofrom the chroot. Both are dropped.SafeConfigParser, so every message through the zeyple filter bounced, and GnuPG 2.4 no longer treatsKey-Type: defaultas RSA.ConfigParseris imported and RSA requested explicitly.mail.err. They are turned off; nothing used them.keys.gnupg.nethad already been retired. Noticed during the migration, fixed here.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_versionas 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 runningdoveconfover the result for the mysql, pgsql and ldap backends.Mechanical renames:
Restructured settings:
plugin { }block is gone; its settings move to the top level..conf.extfiles for sql and ldap are gone: their settings are now written inline, sodovecot-sql.conf.ext,dovecot-dict-sql.conf.ext,dovecot-ldap.conf.extanddovecot-ldap-master.conf.extare replaced byconf.d/auth-sql.conf.extandconf.d/auth-ldap.conf.ext.sieve,sieve_default,sieve_after,sieve_dirandsieve_globalbecomesieve_scriptstorages;imapsieve_mailboxN_*becomemailboxandimapsieve_fromblocks.zlibis replaced bymail_compress;imap_zlibno 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:
quota2table. Quota is now tracked by the count driver and mirrored intoquota2byquota_clonethroughdict_server, so postfixadmin keeps reporting usage. Existingquota2rows are recomputed as mailboxes are accessed.quota_graceno longer accepts a percentage, so the former 10% becomes dovecot's default 10M.verbose_sslis gone;DEBUG_MODEnow enableslog_debug = category=sslinstead, and the assertions follow.LDAP deployments need their
LDAP_DOVECOT_*_ATTRSand_FILTERvalues converted to 2.4 syntax, as those are passed through verbatim. The ATTRS variables stay comma separated and are expanded into Dovecot'sfields { }block, referencing attributes as%{ldap:attribute}. README documents the lookup migration, and the test values are updated accordingly.Further 2.4 breakages fixed here:
quotafilter outranks thequota_storage_sizethe userdb returns per user, so every mailbox was pinned to the 5G default anddoveadm quota getreported 5242880 for everyone. Declaring the default outside the filter keeps it as the fallback for users the userdb gives no quota for — the rolequota_rule = *:storage=5GBplayed in 2.3 — while letting the per-user value win.LDAP_BIND=truedovecot binds as the user and never needs the password hash, and OpenLDAP does not returnuserPasswordto the search. 2.3 ignored the missing attribute; 2.4 expands passdb fields after the lookup and logsNo 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_MODEuncommented every line ofconf.d/10-logging.confwithsed '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.conf.d/20-managesieve.confnow enables the sieve protocol unconditionally (protocols { sieve = yes }), where the 2.3 packaging left that line commented out.conf.dis included after theprotocolsline indovecot.conf, soDISABLE_SIEVEcould no longer close port 4190. Ship our own copy with just the service definitions.login_greeting, so it is "Dovecot ready." rather than "Dovecot (Debian) ready.".quota-warning.shpassed a 2.3-style-o plugin/quota=dict:User quota::noenforcing:proxy::sqlquotatodovecot-ldaso the warning could be delivered without enforcing the quota. 2.4 dropped both theplugin/prefix and the dict driver, anddovecot-ldaignores an unknown-okey 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 authandservice anvilget an explicitclient_limitderived fromDOVECOT_MAX_PROCESS. 2.4 checks it against the sum of the login services'process_limitand prints an advisory on every config parse when it is short. That advisory lands inmail.errwhenever the quota-warning script runs, because dovecot logs a script's stderr at error level, and withDOVECOT_MAX_PROCESS = nproc * 500the 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), sorun.shnow 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 setulimits: nofileon the service in that case.Report a failure to create the vmail user instead of hiding it
groupaddanduseraddhad their output sent to/dev/null, so a collision onVMAILUID/VMAILGIDleft 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
getentcheck so restarts stay quiet, and on failure report the offending UID/GID and touch/etc/setup-error, whichrun.shalready treats as fatal.Adapt the postfix configuration to postfix 3.10
Two changes Debian 13's postfix requires:
smtpd_tls_dh1024_param_fileis 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 everypostconf -htest. Postfix has used its own built-in FFDHE2048 group since 3.7, so the parameter and theffdhe2048.pemit pointed at are both dropped.usr/lib/zoneinfois no longer created in the chroot. Debian'sconfigure-instance.shdeletes 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.pyimportedSafeConfigParser, which Python 3.12 removed (it had been a deprecated alias ofConfigParsersince 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". ImportConfigParserinstead.Key-Type: defaultno longer means RSA, so the accompanyingKey-Lengthis 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
rspamcstreams 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.shdefaulted tohkp://keys.gnupg.net, which was retired along with the SKS pool, soimport-keyandimport-all-keysfailed unless a keyserver was passed explicitly. The default is nowhkps://keys.openpgp.org, which is also dirmngr's own default in GnuPG 2.4, and the README examples no longer citepgp.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.pyreacted 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, andFileClosedNoWriteEvent, 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 (jqoveracme.json,cp -RLTof the letsencrypt live directory), so one genuine write, or a plain read such as traefik loading its store at startup, started an endless cycle: onecerts_helper.sh reloadevery 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, becauseupdate_certsstops 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 tocreated,modified,moved,deletedandclosed(after write), which behaves the same on both watchdog versions.The suite could not see this because the traefik fixtures bind-mounted
acme.jsonas 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/acmesaw 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, replacing1.0.20on Debian 12. Everything above is what that base change required.Type of change
Breaking for LDAP deployments only:
LDAP_DOVECOT_*_ATTRSandLDAP_DOVECOT_*_FILTERare passed through verbatim and must be converted to Dovecot 2.4 syntax. See the migration section in the README.Status
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
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, thenmake default,make reverse,make ldap,make ldap2,make sieve,make ecdsa,make traefik_acmev1,make traefik_acmev2. Not as root: the Makefile passesid -u/id -gin as the vmail user and Dovecot refuses uid 0.A test image built from this branch on overlay
1.1.0is on Docker Hub asmailserver2/mailserver:debian13-test04(digestsha256: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):quota2.bytesandquota2.messagesfor john.doe are non-zero; the seed rows start at 0. Coversquota_clonestanding in for the removed dict driver.tiny.quota's 100 KB quota is accepted underquota_storage_grace;doveadm quota getreports usage over 100%.doveadm searchfinds the "Mailbox quota warning" mail in that mailbox, i.e.quota-warning.shdelivers with enforcement off. Fails on the unfixed script.tiny.quotais refused at LMTP;mail.logcarries thequota_exceeded_messagefor it.mail_compressis in effect.fd limit (ulimit -n) is lower than requiredline inmail.warn/mail.log; present before therun.shchange, and the only test that readsmail.warn.traefik_acmev1,traefik_acmev2andreverse:acme.json(traefik) orfullchain.pem(reverse, letsencrypt mode) in place with identical bytes; the test asserts exactly one "Updating SSL certificates and reloading" and one "Live Certificates match" indocker logs. On the unfixed image the same fixture produced six cycles in about 18 s and this test alone failed.reverse(pgsql):Fixtures behind them: a
tiny.quota@domain.tldmailbox with a 100 KB quota in the mariadb seed, and two extra messages at the end offixtures_default— a filler larger than the quota, then one more that has to be refused. The traefik acme fixtures are mounted as directories, andfixtures_traefik_acmevNand the end offixtures_reverseperform the in-place rewrite once postfix, dovecot and the watcher are up.Updated expectations
Messages scanned7 → 9 andMessages with action no action5 → 7: the two extra fixture messages pass through rspamd before the quota refusal at LMTP. The accounting is in a comment above those tests.verbose_ssl→log_debug,dict sqlquota→dict_server,dovecot-sql.conf.ext→conf.d/auth-sql.conf.ext;login_greetingis "Dovecot ready." now that Debian no longer patches it.usr/lib/zoneinfochroot assertion is dropped: postfix 3.10's Debian packaging removes that directory at startup.Results
default— 128 testsreverse— 81 testsldap— 66 testsldap2— 29 testssieve— 2 testsecdsa— 4 teststraefik_acmev1— 11 teststraefik_acmev2— 12 tests333 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
doveconfagainst dovecot 2.4.1 for the mysql, pgsql and ldap backends.