v2.35.0 (what it said, and what it did)
A minor release, and an unusual one: most of it is CertMate being made to do
what it already claimed. A documentation pass (#875) turned into a list of
eleven places where the page, the form or a comment in the code described
behaviour the code did not have (#876), and closing that list is the bulk of
what is here. Alongside it, a new CA provider contributed from outside, and
several answers the product could not give at all.
The API contract moves to 2.14, from 2.11. Read it from
X-CertMate-API-Version on any response, or api_contract_version in
/api/health. Three MINOR steps, each written down beside the constant in
modules/core/constants.py; nothing was removed or retyped.
The theme, worth naming because it decided what got fixed and what got
rewritten: when the documentation and the code disagree, the documentation is
not automatically the thing that is wrong. A page that promises a guard which
does not exist can be "fixed" by deleting the sentence. Every item below went
the other way unless the promise itself was a bad idea.
Read this before upgrading
A CA you have not configured is now refused, not substituted
If the requested CA's configuration or account could not be loaded, issuance
used to fall back to Let's Encrypt, log a warning, and answer 201. Measured
with nothing configured, six of seven providers did it:
digicert, zerossl, google, sslcom, actalis, private_ca -> issued with letsencrypt
So a request for DigiCert could come back as a Let's Encrypt certificate, and a
request to a private CA for an internal name came back as a publicly
trusted one — which means that name is now in the public Certificate
Transparency logs, for the lifetime of the certificate. A log line does not
undo that.
Requests for an unconfigured CA now fail with 400, naming the CA. Let's
Encrypt still works with nothing saved, because certbot's defaults are its
configuration, and a staging request still stays on staging.
If you were relying on the fallback, you were receiving certificates from a
CA you did not ask for. Configure the CA you want, or ask for one that is
configured.
CERTMATE_CERT_DIR now moves the storage layer too
It moved CertificateManager and left the storage backend writing to a
relative certificates/ path, resolved against wherever the process
started. Since the certificate listing reads through the storage manager first,
an instance pointed at a mounted volume kept a second tree under its working
directory.
An explicit certificate_storage.cert_dir still wins. If you have one set, nothing
changes. If you do not, and you set CERTMATE_CERT_DIR, the storage layer now
follows it — which is what the README always said.
ACME directory URLs must be https
An account-configured directory over plain HTTP is refused. Nothing in the
tree, the tests or the documentation used one; a directory fetched in the clear
is the document that says where to register and where to place orders, and
anything on the path can rewrite it.
Sectigo
Native Sectigo ACME support, contributed by
@QuentinBtd in #884: a registry entry, EAB
credentials, the settings form, the connection test and per-account directory
URLs from SCM.
It also arrived with a behaviour the rest of CertMate did not have — failing
closed when its account could not be loaded — which is the behaviour the CA
change above generalises to every provider.
DigiCert's directory is regional, and its registry entry said operators
override it per account while get_acme_server_url returned the pinned URL
anyway. A customer outside the default region filled in the field, watched the
connection test pass, and had every certificate issued against the wrong
endpoint. Providers now declare whether their directory is pinned, overridable
or account-supplied, and a test reads the requirement from the settings form:
if the form asks for a URL, issuance uses it.
The record
The audit trail could only be read as a tail — limit, capped at 500. On an
instance with any history that made it unanswerable for anything older.
GET /api/activity can be narrowed by operation, resource_type,
resource_id, user and status. The filter is not applied to the tail: the
search walks backwards until it has limit matches or reaches the start of the
log, because "matches among the last hundred" would answer "there are none" for
anything older. The response carries complete, so an empty result that
reached the beginning can be told from one that gave up.
The tail is sealed on a clean shutdown. A signed checkpoint is written
every 100 entries and nothing wrote one on the way out, so entries since the
last checkpoint were unattested. A crash still leaves a tail — work that must
never delay a kill must not run on one — and docs/compliance.md says so
rather than claiming the gap is closed.
The weekly digest is an audit entry, and therefore a SIEM event; it read
the audit log and never wrote to it. It records the outcome, the recipient
count and the figures it reported — not the recipients, who are personal data
this chain cannot take back out.
audit_prune refuses before it deletes. Its check for a signing key could
never fail, because the constructor it was checking created the key when it
found none: pruning from the wrong directory deleted the archived prefix and
signed the replacement anchor with a key the instance had never used. It now
loads without creating, and refuses a bundle exported by a different instance.
Bootstrap actions are attributed to setup_user. Already true, now
documented and pinned: on a current version exactly one user can be created
that way, so more than one means the instance was bootstrapped under an older
one and every user past the first is worth confirming.
Things the interface would not let you say
- Which CA issued a certificate is a column on the certificates table, and a
row in the detail panel (#854). Certificates issued before CertMate recorded
the CA show—rather than a guess. certificate_deployedis published, named and filterable, and was in
neither notification checkbox list — so ticking any box silently excluded it.- Deploy hooks can be asked for
revoked. The event has run since #883 and
the settings page offered two checkboxes for three events. - A CSR-only certificate's key can be rotated by posting the new CSR to
reissue. The page said to submit it "the same way", which read as create —
and create refuses a domain that already has a certificate. The only path was
delete-then-create, with no certificate in between, on the feature chosen
precisely because the key matters. - The version is in the footer of every page, which
help.htmlhad been
telling people for some time (#855). - An optional update check, off by default and staying off. An instance that
was never asked to reach the internet does not, and a check that could not
reach GitHub reportsunknownrather than "up to date".
Credentials that were not where they should have been
- The webhook delivery log keeps the origin, not the whole URL. For Slack,
Discord, ntfy and Gotify the URL is the credential — settings have masked it
for that reason — and the delivery log wrote it in full to disk and returned
it from the API. Entries already on disk are reduced on read. - certbot's stderr is redacted in the log too, not only in the API
response. Some DNS plugins echo the offending credentials line when they fail
to parse it, and "internal" was doing a lot of work in the comment that said
the log could have the raw copy.
Deploy hooks
The examples ran in a container that has none of the commands they used: five
of six could not work, and docs/deploy-hooks.md now says where a hook runs
and names what is absent. The page's blocked-patterns table is checked against
the validator, in both directions — and one row was false, having never matched
the form of . that anyone writes.
Smaller
- A certbot failure no longer reads
Certificate creation failed: Certificate creation failed: …, and a CA rate limit is recognised by the markers the CA
actually sends. Previously "too many failed authorizations" was reported as a
DNS authentication problem, sending operators to rotate working credentials
over a refusal that retrying makes worse. - The image's health-check grace period matches
docker-compose.ymlat 40s. It
was 5s, and a container reaches its first healthy/healthin about 7. - A blocklist is no longer asked about an address it cannot answer for, and
CertMate can be told which resolvers to use for its own lookups (#881) —
the advice to "point CertMate at a resolver of your own" had nowhere to be
followed. - A share-safe backup's listing says what it can still do — restore
configuration onto an instance with no certificates — rather than warning
about installing masks in place of credentials, which is not what happens.
Known
Three tests pass alone and fail under full-suite load, for different reasons
(#905). None is a product defect; all three cost time on a full run, and one
fails in a way that looks like a concurrency regression, which is the worst way
for a test to be wrong.