What's Changed
Added
- Configurable RFC 5280 CA profile — the Create CA wizard exposes signature digest (Auto aligns P-384 to SHA-384 and P-521 to SHA-512, or explicit SHA-256/384/512) and an expandable Certificate Profile section with Key Usage and Extended Key Usage. Roots default to
keyCertSign+cRLSign(nodigitalSignature) and no EKU; issuing CAs adddigitalSignatureand optionalserverAuth, matching common enterprise root/intermediate profiles (Let's Encrypt Root-YR / issuing CA style). Intermediate CAnotAfteris clamped to the parent CA expiry, andGET /api/v2/cas/:idnow returns the X.509 serial number (colon-separated hex) and SHA-1/SHA-256 thumbprints. The obsolete Create CA help step about optional template selection (templates removed in migration 017) has been removed (#160). - ACME external CSR and renewal key reuse — the ACME request form now offers a Key Source selector: Generate new key (default), Reuse key on renewal (preserves the same private key across renewals for DANE/TLSA and key pinning; first issuance generates a key, renewals reload it), or Provide external CSR (paste a PEM CSR; UCM submits it at ACME finalize, the private key never enters UCM). CSR domains are validated against the order identifiers (case-insensitive, RFC 4343). Auto-renewal honours the selected key source. Migration
049addskey_source,csr_pemandsource_certificate_idonacme_client_orders(#161). - ACME staging preflight dry-run — a Run Preflight action on the ACME request form validates domains, contact email, ACME account/EAB, CA connectivity and DNS-01 challenge setup against the Let's Encrypt staging directory without consuming production rate limits or changing the global environment. Two modes: Full (staging order + required DNS TXT records preview) and Validate only (config + connectivity). For manual DNS providers it displays the exact
_acme-challengeTXT records to add, with optional DNS propagation verification. Emits anacme.preflightwebhook event and anacme_preflightaudit entry (#162). - Typed SAN validation for CSR creation — SAN entries are now validated per type (DNS, IP, Email, URI, UPN) on both backend and frontend, with clear cross-type errors (e.g. an FQDN entered in the IP field is rejected with a hint to use DNS). The frontend validation mirrors the backend rules so the client and server stay consistent (#167).
- NIST P-521 EC curve option and normalized EC key labels — Generate CSR and Create CA now accept NIST P-256, P-384 and P-521 (ECDSA) key types through normalized labels (
EC P-256,NIST P-384,secp256r1,prime256v1, …) that map to the OpenSSL curve names used internally. PreviouslyEC P-256failed because the UI label was stripped toP-256and validated against OpenSSL curve names. Thesecp256k1(Koblitz) curve remains intentionally unsupported (#167). - Hex serial number in certificate technical details — the certificate details view shows the serial as colon-separated uppercase hex (browser/OpenSSL display style) alongside the decimal form, copyable. Conversion handles decimal,
0x-prefixed and compact/colon hex inputs and supports serials larger than 159 bits via arbitrary-precision integer math.
Fixed
- API key permission escalation — a non-admin user with permission to create API keys (
POST /api/v2/account/apikeys) could mint a key carrying permissions their own role did not hold (e.g. a viewer grantingadmin:system). The create path now rejects any permission the creator does not have, with the same wildcard semantics as the auth checker. Sharing a wildcard key (*) requires the creator to be an admin (#163). - Forged mTLS enrollment via spoofed proxy headers —
POST /api/v2/mtls/enrollhonoredX-SSL-Client-Verify,X-SSL-Client-S-DNandX-SSL-Client-Certheaders from any peer, so a caller who could reach gunicorn directly could forge a client certificate and enroll it as another user. The endpoint now gates onis_request_from_trusted_proxy()for those headers, matching the existing protection on thelogin_mtls()path (#163).
📜 Recent release history (last 2 versions)
[2.183] - 2026-07-03
Added
- Operator-configurable HSTS (Strict-Transport-Security) header — the HSTS policy was previously hardcoded to
max-age=31536000; includeSubDomainson every HTTPS response. Instances serving self-signed certificates during initial setup can now opt out entirely, drop theincludeSubDomainsdirective, or shortenmax-agefrom Settings → Security. The setting can also be forced viaUCM_HSTS_ENABLED,UCM_HSTS_INCLUDE_SUBDOMAINSandUCM_HSTS_MAX_AGEenvironment variables in/etc/ucm/ucm.env(takes precedence over the database); when set, the corresponding toggle in the UI is locked with a badge (#154). - mTLS PKCS#12 export from Account — after generating an mTLS certificate from the Account page, both PEM and PKCS#12 (.p12) download are now offered client-side, with password-protected PKCS#12 for browser or OS keychain import. The download endpoint enforces POST with a JSON body for PKCS#12 (password never sent via query string, avoiding leakage in proxy logs), minimum 8-character password, and returns the
AuthCertificateid usable by list/download/export (#156).
Fixed
- WebSocket handshake fails when if HTTPS_PORT was set to 443 -
CORS_ORIGINSalways appended:{HTTPS_PORT}to every allowed origin, so on port 443 the list contained entries such ashttps://ucm.example.com:443. Browsers omit the port number from theOriginheader when it is the scheme default so it would never match. As Socket.IO performs server-side checks onOrigin, WebSocket connections would be silently rejected.CORS_ORIGINSnow omits the port suffix whenHTTPS_PORTis 443 (#155). - SSH setup script command injection via hostname — the public
GET /ssh/setup/<refid>endpoint accepted arbitraryhostnamequery values that were embedded into generated shell/PowerShell scripts without validation. A payload such as$(id)or";id;"would execute when an operator piped the script to bash. Hostnames are now validated against^[a-zA-Z0-9._-]+$via a shared helper on both the public and authenticated setup routes, and the Windows script generator escapes single quotes (#157).
[2.182] - 2026-07-02
Added
- Per-CA ACME timing settings and robust DNS-01 TXT verification — each external ACME CA account now carries its own order poll timeout, poll interval and HTTP timeout (migration
048addsacme_client_accounts.order_poll_timeout_sec,order_poll_interval_sec,http_timeout_sec), so a slow authority no longer inherits the global hardcoded values. The DNS-01 challenge self-check now resolves the expected TXT record through the authoritative nameservers first, then public resolvers, with per-resolver diagnostic logging, and invalid ACME authorizations are detected during polling instead of stalling until timeout. The Gandi DNS provider was also hardened (URL-encoding of record values, post-create verification, missingAnyimport) (#150).
Fixed
- ACME auto-renewal no longer crashes when refreshing the order expiry — the renewal service read
new_cert.not_afterto copy the new certificate's expiry onto the order'sexpires_at, but theCertificatemodel exposes that date asvalid_to. TheAttributeErroraborted the renewal right after the new certificate had been issued and imported, soexpires_atwas never updated and the scheduler re-requested the same certificate on every tick. The renewal path now readsvalid_to, andexpires_atis updated correctly so a renewed certificate is not renewed again until its next due window.
Added
- Multi-CA management for the ACME client — UCM can now issue certificates from several external ACME authorities (Let's Encrypt, Actalis, ZeroSSL, Google Trust Services, HARICA…) instead of a single one. ACME CA accounts are managed from the UI (CRUD, per-account External Account Binding, default selection, registration status), each certificate request picks its issuing CA, and the order is pinned to that account so renewals stay on the same authority. The
AcmeClientOrder.acme_client_account_idforeign key (migration047) links each order to its external CA account;AcmeClientService.for_issuance(environment, account_id=)resolves explicit account > configured custom directory > Let's Encrypt environment, andfor_order(order)reuses the pinned account on verify/finalize/status/renewal (#149).
Full history: CHANGELOG.md
Installation
Docker (Recommended)
# From Docker Hub
docker pull neyslim/ultimate-ca-manager:2.184
# Or from GitHub Container Registry
docker pull ghcr.io/neyslim/ultimate-ca-manager:2.184
# Run
docker run -d -p 8443:8443 \
-e SECRET_KEY=$(openssl rand -hex 32) \
--name ucm neyslim/ultimate-ca-manager:2.184Debian/Ubuntu
wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.184/ucm_2.184_all.deb
sudo dpkg -i ucm_2.184_all.deb
sudo apt-get install -fFedora/RHEL
wget https://github.com/NeySlim/ultimate-ca-manager/releases/download/v2.184/ucm-2.184-1.fc43.noarch.rpm
sudo dnf install ./ucm-2.184-1.fc43.noarch.rpmSilent/Automated Install
# Skip firewall prompts for CI/automation
sudo UCM_PORT=8443 UCM_FIREWALL=no dpkg -i ucm_2.184_all.debDefault Credentials
- Username:
admin - Password:
changeme123
Change the password immediately after first login!
Documentation
- Installation Guide
- API Documentation