github NeySlim/ultimate-ca-manager v2.214

latest releases: v2.235, v2.235-rc1, v2.234...
one month ago

What's Changed

Security

  • Certificates: a valid, non-revoked X.509 certificate can no longer be deleted — deletion removed the record while the certificate stayed trusted until expiry, with no CRL/OCSP trace; revoke it first so relying parties see the change (single delete returns 409, bulk delete reports the item as failed). Expired or revoked certificates, and CSR-only records, are unaffected (#296, contributed by @gb-123-git & PMGA Tech LLP)
  • Revocations now survive certificate deletion — a new persistent revoked_serials store keeps every revoked or superseded serial on the CRL and answering revoked over OCSP until the original certificate's notAfter, even after the certificate record itself is deleted. A revocation and its persistent record are written in one transaction, and removing a certificate hold deletes both atomically, so relying parties can never observe a half-applied state (migration 078) (#297, contributed by @gb-123-git & PMGA Tech LLP)
  • Renewing a revoked certificate is refused (409) instead of silently clearing its revocation state — issue a new certificate instead (#297, contributed by @gb-123-git & PMGA Tech LLP)
  • Renewed private keys are encrypted at rest exactly as at issuance — bulk renewal previously stored them unencrypted, silently downgrading deployments with a master key configured (#297, contributed by @gb-123-git & PMGA Tech LLP)

Changed

  • Certificate renewal is now in-place — manual, bulk and automatic renewal share a single implementation that updates the existing certificate record: id and refid never change (API consumers and integrations keep their stored references), created_at preserves the original issuance date, and new renewed_at / renewed_times fields track the renewal history. The superseded serial is published on the CRL (reason superseded) and over OCSP until the old expiry. Certificates whose key UCM holds are re-keyed on renewal; protocol-enrolled certificates (SCEP/EST/ACME) are re-signed with their existing public key, since the private key lives on the client (#297, contributed by @gb-123-git & PMGA Tech LLP)

Added

  • CA signed by an external CA (CSR) — a new CA creation mode for the canonical offline-root / online-issuing-CA pattern: UCM generates the key pair (local or HSM-backed) and a CA-flavored CSR (basicConstraints CA:TRUE and keyCertSign/cRLSign, both critical), and the CA waits in an "awaiting certificate" state with the CSR downloadable — the private key never leaves UCM. Uploading the certificate signed by the external root (PEM or DER) activates the CA after strict checks: the certificate's public key must match the stored private key, CA BasicConstraints/KeyUsage are enforced, the effective pathLen comes from the signed certificate, and the chain links automatically when the external root was imported (certificate only; AKI→SKI match with issuer-DN fallback, signature verified). Renewal re-issues a CSR from the same key so the SKI stays stable across renewals, and a pending CA is excluded from every signing, CRL, OCSP and protocol path until activated (migration 079). Smoke-tested end-to-end on SQLite and PostgreSQL. Requested in #298
  • Optional auto-purge of stale revoked-serial records (expired ones only) during CRL generation — default off, so records are kept as renewal-chain history unless an admin enables it (#297, contributed by @gb-123-git & PMGA Tech LLP)

Fixed

  • Auto-renewal notification settings are now honored — the notify toggles were never read back (notifications always sent) and boolean values did not survive a settings round-trip; malformed JSON in the auto-renewal configuration no longer crashes the scheduler (#297, contributed by @gb-123-git & PMGA Tech LLP)

📜 Recent release history (last 2 versions)

[2.213] - 2026-08-19

⚠️ Upgrade note — in-flight ACME proxy orders placed with a bare JWK must be
re-created.
The proxy's new-order now requires a kid (RFC 8555 §6.2) and binds
every order to an RFC 7638 thumbprint. A pending proxy order created before this
release through a jwk-signed new-order carries only the legacy thumbprint of the
raw JWK dict, which no longer equals the value derived from the requesting key — and
the account-row reconciliation cannot resolve it either, because that legacy hash
never equals a stored account thumbprint. Those orders become inaccessible and must
be re-created. ACME automation self-heals (the client simply places a new order on
its next run), and already-issued certificates are unaffected — but a client that
polls a saved order URL forever will need a nudge.

Security

  • ACME proxy new-order requires a kid — a jwk-signed JWS is verified against its own inline key with no account lookup, so accepting one skipped the account existence/status check and the per-EAB identifier restrictions whenever EAB was not mandatory, and left the resulting order with no owner recorded — which the ownership check then served to every other client of that proxy. Identifier and challenge-type validation still runs first, so a client with an unsupported identifier keeps getting unsupportedIdentifier rather than an authentication error (#260)
  • Deactivated and revoked accounts are refused on new-order — the status check only covered deactivated, so a revoked account could keep ordering certificates
  • Proxy resources require a positive owner match — authz, challenge, order and certificate access previously allowed any verified requester when an order's owner binding was half-populated (an account id without a stored thumbprint, or a thumbprint that resolved to no account): the absence of a contradiction was treated as authorization. A match is now required on one of the two fields, and a half-populated binding is reconciled through the owning AcmeAccount row rather than waved through (#260)
  • Owner thumbprints follow RFC 7638 — the thumbprint was computed over the raw JWK dict, so a client sending optional members (alg, kid, use) turned a legitimate owner into a mismatch; only the required members are hashed now, by a single implementation shared with the value stored on the account
  • Upstream authorizations are matched exactly — the candidate lookup is a LIKE '%url%' prefilter, which also matches a prefix of a stored URL (.../authz-v3/99 inside .../authz-v3/999); a client could bind a foreign authorization to an order it does own and pass the ownership check with it. Prefilter hits are now re-checked against the decoded URL list
  • Challenge-to-order resolution has no approximate fallback — the owning order is resolved from the recorded authz mapping, from the challenge's parent authz path, or from the upstream Link rel="up" header only. There is no host-only match and no "most recent pending order" guess, either of which could run DNS-01 automation against a foreign domain; rel="up" is also selected explicitly rather than taking the first link, since rel="index" is commonly listed first
  • The certificate-download fallback scan is owner-scoped — resolving an untracked certificate URL scanned every pending proxy order and signed one upstream POST per candidate, letting any caller drive the proxy's upstream account (cross-tenant probing, rate-limit burn). The scan is now restricted to rows the requester could own and capped
  • Ownership is verified before any upstream call — a denial no longer costs a signed request to the CA
  • A challenge whose identifier cannot be determined fails closed — on a multi-SAN order the first domain is not necessarily the one being validated; publishing the DNS-01 TXT record under that name was wrong. The request now fails with a problem document naming the cause
  • SSH certificates: a valid, non-revoked certificate can no longer be deleted — deletion silently dropped the record while the certificate stayed trusted by servers until expiry; revoke it first (the API returns 409 otherwise) (#292, contributed by @gb-123-git)

Fixed

  • Azure Key Vault HSM provider: key generation passed an invalid hsm argument to create_rsa_key/create_ec_key and failed with a TypeError — the SDK keyword is hardware_protected (#295, contributed by @jeancarlor)
  • Discovery: large scheduled scans no longer exhaust memory and crash-loop the service — probes are submitted through a bounded window with connection-level failures discarded immediately, a scan is capped at 65,536 host×port probes (rejected with a clear error beyond), a profile's next-run timestamp advances when a scan starts (not when it completes), scans orphaned by a restart are marked failed at startup, and reverse-DNS lookups are now lazy (#293)
  • Bulk Actions: pagination, search and status/CA filters for certificates and CSRs now run server-side against the complete inventory — the page-size selector is respected, selections survive page changes, resource count chips show real totals, and the CSR list endpoint gained server-side search (#294)
  • Background DNS-01 threads no longer share ORM state with the request that started them — the worker rebound the request's account object to its own session, racing the request thread and the sibling threads a multi-domain order starts; each worker now signs through its own service instance

Changed

  • Proxied ACME requests reuse the upstream directory, finalize URL and challenge mapping, and pool Replay-Nonce values — every proxied call previously paid an extra GET /directory plus HEAD /new-nonce upstream. All caches are advisory: a miss falls back to the upstream round-trip, so a restart or a second worker costs performance, never correctness

[2.212] - 2026-08-17

Added

  • API keys: proper revoke/delete lifecycle — revoke an active key, permanently delete a revoked or expired one; expired keys no longer count against the per-user limit; "Last used" now shown in the account page (#291, contributed by @Hemsby)
  • ACME local domains: bare private TLDs (e.g. local, internal) can now be registered, covering all their subdomains through the existing parent matching (#290, contributed by @gb-123-git)

Fixed

  • ACME server: http-01 validation now follows HTTP redirects (RFC 8555 §8.3) — a site-wide http→https 301 on the challenge path no longer fails with "Key authorization mismatch". Up to 5 hops, http/https on default ports only, TLS not verified on https hops (the target usually serves the certificate being renewed), and every hop re-vetted by the SSRF policy (cloud metadata always refused; targets resolved, vetted and pinned when private IPs are disallowed)
  • Deleting a certificate (single or bulk) or a CA now removes its cert/key/csr files on disk instead of leaving them orphaned (#289, contributed by @Hemsby)
  • app.config['DATA_DIR'] is now set, so the session cleanup task no longer silently falls back to the default data directory on custom layouts (#290, contributed by @gb-123-git)

Full history: CHANGELOG.md


Installation

Docker (Recommended)

# From Docker Hub
docker pull neyslim/ultimate-ca-manager:2.214

# Or from GitHub Container Registry
docker pull ghcr.io/neyslim/ultimate-ca-manager:2.214

# Run
docker run -d -p 8443:8443 \
  -e SECRET_KEY=$(openssl rand -hex 32) \
  --name ucm neyslim/ultimate-ca-manager:2.214

Debian/Ubuntu

wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.214/ucm_2.214_all.deb
sudo dpkg -i ucm_2.214_all.deb
sudo apt-get install -f

Fedora/RHEL

wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.214/ucm-2.214-1.fc43.noarch.rpm
sudo dnf install ./ucm-2.214-1.fc43.noarch.rpm

Silent/Automated Install

# Skip firewall prompts for CI/automation
sudo UCM_PORT=8443 UCM_FIREWALL=no dpkg -i ucm_2.214_all.deb

Default Credentials

  • Username: admin
  • Password: changeme123

Change the password immediately after first login!

Documentation

Don't miss a new ultimate-ca-manager release

NewReleases is sending notifications on new releases.