v2.48.2 (scoped keys and every name of a certificate, one DNS-01 validation per record, and hashed dependencies)
v2.48.2 (scoped keys and every name of a certificate, one DNS-01 validation per record, and hashed dependencies)
A patch release. An API key restricted with allowed_domains now acts on an existing certificate only when its scope covers every name the certificate covers, as creation already required. Two certificates that answer DNS-01 at the same record no longer validate at the same time. What the image installs is now checked by hash, and so are CI's tools and the installer's uv. The API contract stays at 2.42: no field changes shape.
Correction, added after the release. No field changes shape, but a status code does change for an existing condition: a key restricted with
allowed_domains, on a certificate that also covers a name outside its scope, was answered 200 and is answered 403. By this project's own rule that moves the contract version, and it was not moved here. The image tagged v2.48.2 reports 2.42; the number becomes 2.43 in the next release, whose history line records this change with the others of its kind.
Read this before upgrading
If you use API keys with allowed_domains
Creating a certificate has always required the key's scope to cover every name the certificate asks for. Operations on a certificate that already exists now apply the same rule: the key's scope must cover every name the certificate covers. Those names are the certificate's own, the san_domains recorded with it, and the DNS names in the certificate itself.
This applies to: reading a certificate and its deployment status, browser deployment reports, both download routes, changing its configuration, auto-renew, renewing (API and dashboard), reissuing, deploying, deleting, the reissue-keyless sweep, the certificate list and the dashboard's batch download. A reissue is checked against the names the certificate has today as well as the names requested.
A key whose scope covers only some of a certificate's names now gets 403 DOMAIN_OUT_OF_SCOPE for that certificate, and the certificate is no longer in that key's list. The refusal names only the domain in the request. The audit entry records that the certificate covers a name outside the key's scope, without naming it.
Sessions, unscoped keys and the legacy bearer token are not affected.
If a scoped key should keep working with a certificate that also covers another team's name, add that name to the key's scope, or split the certificate in two.
If two certificates validate at the same DNS record
example.com and *.example.com are two certificates that both write _acme-challenge.example.com. So are two certificates whose challenges are delegated to the same alias. Up to two issuances run at once by default (CERTMATE_ISSUANCE_WORKERS), and some DNS plugins write the whole record set with only their own value: certbot-dns-route53 does. One validation could then remove the other's value.
CertMate now holds one lock per challenge record while certbot runs, in create and in renew. A second certificate that needs a record in use is refused with 409 DOMAIN_OPERATION_IN_PROGRESS, and the message names the record. A request made with async fails its job with the same message and error_code. The nightly sweep tries again the next night. Certificates whose records do not overlap still run in parallel.
If you script issuance of a wildcard and its apex as two certificates, issue them one after the other, or retry on the 409.
If you build an image on top of CertMate's lockfile
requirements.lock and requirements-minimal.lock now carry the sha256 of every file. pip turns hash checking on for a whole install when any line it reads carries a hash, constraints included. So pip install -c requirements.lock <your extras> now fails with "Hashes are required". Constrain your extras with requirements.constraints (or requirements-minimal.constraints): the same pins, without hashes. The EXTRA_REQUIREMENTS build argument already does this.
Fixed
- Scope of API keys over every name of a certificate (#1158), above.
- Two DNS-01 validations at the same record (#1157), above.
Changed
- The image installs its dependencies by hash (#1154).
requirements.lockandrequirements-minimal.lockcarry hashes and are installed with--require-hashes. The builder's pip, setuptools, wheel and packaging come fromrequirements-build.lock, with hashes, at the versions the 2.48.1 image carries. No version moves:pip freezein the image is identical to 2.48.1's. - CI's tools and the Galaxy publish job are pinned by hash (#1155): pip from
requirements-build.lock, flake8 fromrequirements-lint.lock, ansible-core fromrequirements-galaxy.lock(2.20.9). deploy/install.shchecks uv's installer before running it (#1155). It downloads the installer for the pinned uv version, compares it withUV_INSTALLER_SHA256, and refuses to run one that does not match.- A certificate's file paths are built in one place (#1156). Nothing a user can see changes. It is the first step towards certificates whose name differs from their domain (#854).
Documentation
- SECURITY.md, "Supply-chain posture", says what is verified by hash and where that stops. CONTRIBUTING's "Changing a dependency" follows.
- The README and the API reference state the scope rule above.
How it was checked
- Scope: the real application with real certificates on disk and a real scoped key. The test certificates are: one whose extra name is outside the scope in its record and in the certificate; one where it is only in the certificate; one where it is only in the record; one with no private key left; and two controls with every name in scope. Without this change, 18 of the 20 route tests fail, and the two that pass are the controls. Each switched check, reverted on its own, is caught with a 200 or 422 where 403 is expected.
- Challenge records: the real
create_certificateandrenew_certificatewith a certbot stand-in held "running": a wildcard issuance and a wildcard renewal are refused while their apex validates, and an unrelated certificate runs alongside. Through the create route, the second request gets the 409 with the record named. Five deliberate breakages (no lock in create, none in renew, the wildcard not reduced to its base, unsorted acquisition, locks never released) are each caught. - Hashes: the default image built from the hashed lock has the same
pip freeze --allas the published 2.48.1 image (126 packages, pip included). With one hash altered,docker buildstops ("THESE PACKAGES DO NOT MATCH THE HASHES"). The uv installer snippet, run in Debian 12, installs with the pinned sha256 and refuses with one digit changed. - The release gate runs on the release commit: the suite, the browser tests, and real certificates from Let's Encrypt staging.
Not verified
- Lock holding across processes. The challenge-record locks are per process, like the per-certificate lock. The image runs one worker. Two CertMate instances sharing a DNS zone are not coordinated by this.