Repository navigation
v2.49.0 (certificates CertMate did not issue: imported with a CA for their next renewal, or external and replaced by their owner)
Latestv2.49.0 (certificates CertMate did not issue: imported with a CA for their next renewal, or external and replaced by their owner)
A minor release. A certificate issued elsewhere can now be imported without issuing anything, either with a CA configured here for its next renewal (#1227), or as an external certificate that no CA here renews: CertMate stores, deploys and watches it, and its owner replaces it by upload (#1159). The CA account's own email now counts at every gate before issuance (#1228). An adopted certificate runs its deploy hooks once (#1237). The API contract moves from 2.44 to 2.48.
Read this before upgrading
Nothing needs to change on your side. No certificate CertMate issued renews differently, and no setting or file format changes. What a caller may notice:
A CA that is not configured is refused before anything starts
A create or a reissue naming a CA other than Let's Encrypt that is not configured on this instance used to be accepted and fail later; an async request answered 202 and its job failed. It now answers 400 with the CA's reason, and no job is queued (#1228). The dashboard's batch create says the CA or its account is not configured, without the text of the underlying error. Let's Encrypt is unchanged: with no saved configuration, certbot's defaults still apply, staging included.
The CA account's email counts at every gate
Issuance already used the selected CA account's email ahead of the global one. The checks before it required the global email even so, and refused a create, a reissue, a batch or an adoption with only an account email. They now resolve the email the way issuance does: the account's, then the global one, and a refusal only when neither is set (#1228).
If you call the API
By contract number:
- 2.45
POST /api/certificates/import; certificate reads gainimported_atandca_account_id, andrenewal_info.statusgainsimported. - 2.46
external: trueon the import; reads gainexternal, andrenewal_info.statusgainsexternal;EXTERNAL_CERTIFICATEandNAMES_ALREADY_MANAGED(409). - 2.47
POST /api/certificates/<domain>/replace; reads gainreplaced_at; an external certificate reads withrenews_atnull. - 2.48 a create, reissue, batch or import through a CA that names what an external certificate covers answers 409
NAMES_ALREADY_MANAGED.
Every change is in docs/api.md.
New
- Import an existing server certificate (#1227, contributed by QuentinBtd).
POST /api/certificates/importstores a certificate, its chain and its key, checked for dates, key match, chain signatures and constraints, without asking any CA. The request names the CA, account and challenge for its next renewal, andauto_renewdecides whether the sweep renews it when due; the first renewal makes a normal certbot lineage, and the imported certificate keeps serving if it fails. In the dashboard: Import an existing certificate in the create form, with the identity of the certificate shown before anything is sent and the three files checked together. - External certificates (#1239, #1240, #1241, #1242; #1159). For a certificate from a CA CertMate cannot reach, a vendor portal or an internal CA without ACME, import it with
external: true, or Renewal: Not by CertMate in the dashboard:- It is stored, listed, downloadable, deployed and watched for expiry like any other, and never issued, renewed or reissued. Every path that would ask a CA for it answers 409
EXTERNAL_CERTIFICATE, the renewal sweep counts it underexternaland leaves it alone, and its expiry warnings come as for a certificate with auto-renew off. - Its owner replaces it by upload, with
POST /api/certificates/<domain>/replaceor Replace certificate in its panel. The four files are published together as a renewal publishes them, deploy hooks run withCERTMATE_EVENT=renewed, and the probe, notes and tags are kept. An upload of the certificate already in place is refused, and so is one that expires earlier, unless the request says that is meant. - A name belongs to one certificate: an external import cannot take a name a managed certificate covers, and a create, reissue, batch, adoption or import through a CA cannot take one an external certificate covers. Two certificates CertMate issues may still share a name, as today.
- "External" is recorded twice, in the certificate's metadata and in its settings entry, and either one is enough. A missing CA otherwise means "the default CA", so losing one record must not turn the certificate into one CertMate reissues.
- It is stored, listed, downloadable, deployed and watched for expiry like any other, and never issued, renewed or reissued. Every path that would ask a CA for it answers 409
Fixed
- An adopted certificate ran its deploy hooks twice (#1237). Adoption announced the new certificate once from the issuance and once more from the adoption route; it is now announced once, with the adoption marked on the same event.
- The MCP renew tool described itself as a forced renewal (#1238). It never sent
force, so a certificate that was not due was not renewed. The description now says what the tool does, and the tool still does not force. - The CA account's email, and a CA that is not configured (#1228, contributed by QuentinBtd), as described above. An adoption plan for a CA that is not configured says so, and still reports the email and DNS provider that are configured.
How it was checked
-
The end-to-end bench with a private CA, rebuilt from
mainat9cae3dd, with the scheduler's real renewal job:- an external certificate was imported, and the webhook receiver got
certificate_createdwithexternal: true; - renew, force renew, reissue and turning auto-renew on answered 409
EXTERNAL_CERTIFICATE, and a create naming its SAN answered 409NAMES_ALREADY_MANAGEDbefore any issuance; - the real renewal job renewed the bench's 11 other certificates through the private CA and reported "1 external", leaving the external certificate's serial unchanged;
- a replacement expiring earlier was refused; one expiring later was served, its certificate and key matching, and the receiver got
certificate_renewedwithreplaced: true; - a new certificate was issued from the private CA over HTTP-01 beside it.
Before #1227 was merged, two certificates from a vendor-style root were imported with the private CA as their renewal source: with
auto_renewon the real job renewed one through that CA into a normal lineage, and with it off the job left the other alone until the switch was turned on. - an external certificate was imported, and the webhook receiver got
-
The dashboard in a browser: the browser suite against an image built from #1241 (142 passed, 3 skipped, 1 expected failure), with the new tests for the external import form, the row, the panel and the replacement.
-
Deliberate breakages: 21 in the external-certificate changes (the import lock, each record of the marker, each refusal of the replacement, each part of the name rule, what the dashboard sends); each was caught by the intended test.
-
The suite (8,805 tests) on
mainat9cae3dd, and the coverage floors of the HTTP layer. The release gate runs the suite, the browser tests and real certificates from Let's Encrypt staging on the release commit.
Not verified
- A certificate from a public vendor's CA as an external certificate. The bench and the tests use chains made for the purpose; no certificate bought from a vendor was imported.
- A real Sectigo or other paid account for the import's first renewal and for #1228's email rule. Both are covered with the CA mocked, and the renewal with the private CA.
- Azure Key Vault. The external marker and
replaced_atsurvive its tags in tests; no live vault was used. - An external import and a create of the same name started at the same moment. Both can pass; the window is documented, and #854's name reservation is what closes it.