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.
With more than one certificate resolver in
acme.json, the mailserver extracts no certificate at all and silently serves the snake-oil one instead. That is #62, open since January 2024, and it is not specific to the Traefik version in that report — the run below is a file written by Traefik 3.6:Fixes #62
What is the current behavior (you can also link to an open issue here) ?
dumpcerts.traefik.v2.shreads the ACME account key withjq -e -r '.[].Account.PrivateKey', which returns one key per certificate resolver. Both keys are then piped intoopenssl rsaat once, openssl refuses the input, andset -o errexitaborts the script — before the loop that extracts the certificates has run. One resolver works; two produce nothing.What is the new behavior (if this is a feature change) ?
The
acme.jsonwritten by Traefik 2 and 3 is extracted withtraefik-certs-dumper, which handles any number of resolvers. Everything downstream is unchanged: same file names, same wildcard handling, same reload path.The key it dies on is never used
The account key is written to
private/letsencrypt.keyand nothing in this repository reads it — notcerts_helper.sh, not Postfix, not Dovecot. The certificates the mailserver actually needs sit in.[].Certificates[]and are extracted after the line that fails.Why a tool rather than a patch
dumpcerts.traefik.v2.shis an upstream file (Brian Harrington'sdumpcerts.sh), so patching it here would fork it.traefik-certs-dumperis in the base image sincedebian-mail-overlay:1.1.0(so inmailserver:1.2.0), and is refreshed to its latest release every time the overlay image is rebuilt.It is a drop-in: its default layout is the one the script produced,
certs/<domain>.crtandprivate/<domain>.key, socerts_helper.shkeeps working unchanged around it.acme.jsondumpcerts.traefik.v2.shtraefik-certs-dumpercerts/<domain>.crt,private/<domain>.keycerts/*.domain.tld.crt"Certificates": nullThe script is deleted rather than left unused.
The two older formats keep their scripts
traefik-certs-dumpercannot read the Traefik 1.5/1.6acme.json(.PrivateKeywith.DomainsCertificate.Certs[]): given one, it writes an empty dump and exits 0, which would turn a broken extraction into a silent fallback to the self-signed certificate. So the detection chain incerts_helper.shis untouched and only its third branch changes.For the Traefik 1.7 format (
.Account.PrivateKey) the tool's--version v1output is identical todumpcerts.acme.v2.shexcept for the PEM header of the unused account key, so that branch could move too — deliberately not done here, as it is not part of the bug.--clean=false, and a temporary directory that was never emptiedThe destination is
/tmp/ssl, which already containsnormalized/, the directory the final certificates are assembled in; the tool's default--cleanwould delete it. Cleaning the directory iscerts_helper.sh's own job, except that it never did it:Both globs are now outside the quotes, so stale files from an earlier run are really removed before a new dump.
Say "cannot read" rather than "unknown format"
An unreadable
acme.jsonwas reported as an unknown format, and that message was only ever appended to the dump log, never printed — sodocker logs mailservershowed three rawjq: error: ... Permission deniedlines and nothing else. This is what the reporter of #62 spent a year chasing, on a host with SELinux enforcing. It now says what is wrong:An unreadable
acmefolder was worse:[ -f "$ACME_FILE" ]fails, so the mailserver reported "Traefik SSL certificates not used" and generated a self-signed certificate as if no volume had been mounted at all. A mounted but unreadable folder is now called out before the fallback:Neither message can misfire when the mount is absent:
/etc/letsencryptdoes not exist in the image.Two more lines in the same block were wrong rather than unclear: the failure hint pointed at
/mnt/docker/traefik/acme/dump.log, which nothing has ever written — the log isacme_dump.login the mail volume — and the advice mentioned a Traefik 1 "frontend rule".The format everyone uses had no test coverage
test/share/traefik/acme.v1is the Traefik 1.5/1.6 format andacme.v2is the Traefik 1.7 one. The format every current user has — what Traefik 2 and 3 write — was not covered by any suite, which is why this bug survived two years of green CI.traefik_v3covers it, with a fixture generated by Traefik 3.6 against a local Pebble ACME server, holding two certificate resolvers (mail.domain.tldandspam.domain.tld) and so reproducing #62 directly. One of its assertions is that the fixture still has more than one resolver, so the regression cannot be quietly removed by a later edit of the fixture.Type of change
Status
Todo List
How has this been tested ?
Suite by suite, not as root, after
git submodule update --init --recursiveandmake build:traefik_v3— 9 tests, newtraefik_acmev1— 11 teststraefik_acmev2— 12 testsecdsa— 4 testsdefault— 128 tests0 failures.
reverse,ldap,ldap2andsievewere not run locally: they take the same self-signed certificate path asdefault, and CI runs them.The new suite fails on the current image, which is what makes it a regression test rather than a description of the fix. Same fixture,
NAME=mailserver2/mailserver:1.2.0:acme.json existacme.json has more than one certificate resolverone write to acme.json causes exactly one reloadthe dump log was removedall certificates were generatedcheck private keyprivate key matches the certificatethe certificate from acme.json is the one served/var/log/mail.err does not existBeyond the suite,
certs_helper.sh update_certswas run against files written by Traefik 3.6 in three shapes, checking each time that/ssl/privkey.pemmatches/ssl/cert.pem:acme.json[ERROR] The certificate for mail.domain.tld ... was not found ![INFO] Let's encrypt wildcard certificate found, certificate extractedThe fixture is reproducible without a public domain or a Let's Encrypt account: run
ghcr.io/letsencrypt/pebblewithhttpPort80 andtlsPort443, point twocertificatesResolvers(onetlsChallenge, onehttpChallenge) athttps://pebble:14000/dir, give the Traefik container--network-alias mail.domain.tld --network-alias spam.domain.tldso Pebble resolves the challenge through Docker's DNS, and let Traefik trust Pebble withLEGO_CA_CERTIFICATES=/pebble.minica.pem.