-
Notifications
You must be signed in to change notification settings - Fork 11
Release Notes v2.128 to v2.140
NeySlim edited this page Apr 27, 2026
·
1 revision
Consolidated release notes for all stable releases between v2.128 (2026-04-21) and v2.140 (2026-04-27).
For older versions see the individual release pages and the full CHANGELOG.
-
Certificate SAN database columns now derived from the final SAN list (#94). When a CN is auto-promoted to an
rfc822NameSAN at issuance, thesan_email/san_dns/san_ip/san_uricolumns are now written from the canonical SAN list instead of the raw form payload, so DB queries match the X.509 extension. Migration027_backfill_san_emailre-parses existing certificate PEMs and backfills any rows that were out of sync (idempotent on SQLite and PostgreSQL). Thanks @Hemsby. -
Certificate and CA files written to disk on creation (#95). Added SQLAlchemy
after_insertlisteners on theCertificateandCAmodels that immediately materialize.crt/.keyfiles underdata/certs/anddata/cas/for every creation path (UI, CSR signing, ACME, SCEP, import). The startup file-regeneration scan is kept as a safety net. File-write errors are logged but never abort the database transaction. Thanks @Hemsby.
-
ACME External Account Binding (EAB) — RFC 8555 §7.3.4. Full EAB credentials manager (backend models, API, UI under ACME → EAB Credentials). Operators can issue, list, rotate and revoke
kid/hmacpairs; clients (cert-manager, certbot, acme.sh) bind their account onnewAccountvia JWS over the MAC key. Brings UCM in line with public ACME CAs (Let's Encrypt EAB, ZeroSSL, Google Trust Services). -
ACME custom DNS resolvers for DNS-01 validation. Per-account override of system resolvers when validating
_acme-challengeTXT records. Useful for split-horizon DNS, internal authoritatives, or when public resolvers cache stale records during automated renewals. -
ACME on internal / private IPs — gated by
acme.allow_private_ipsSystemConfig (defaulttrue). HTTP-01 and TLS-ALPN-01 validation now works out of the box for RFC1918, loopback,.lan/.local/.corptargets — UCM's primary deployment model. Cloud metadata IPs (169.254.169.254) remain blocked unconditionally. -
Kubernetes & cert-manager integration. Reference manifests under
examples/kubernetes/cert-manager/(HTTP-01 ClusterIssuer, DNS-01 ClusterIssuer with EAB, sample Certificate, EAB Secret template, README). Full integration guide on the wiki and on https://ucm.tools/docs.
-
ACME audit & RBAC hardening. Challenge state transitions now produce audit records on terminal states (
valid/invalid) instead of every poll, eliminating audit log noise.account.key_change(RFC 8555 §7.3.5) is audited.delete:acmepermission added to theoperatorrole to matchwrite:acme. -
ACME backup/restore parity.
acme_eab_credentialsis now exported and restored alongsideacme_accounts; full account fields (contact, status, terms-of-service, external-account-binding metadata) are now round-tripped end-to-end.
-
backend/services/ssh_cas.py— converted f-strings containing escape sequences to raw f-strings to silence PythonSyntaxWarning: invalid escape sequenceon 3.12+.
-
CAs page silently dropped CAs beyond the first 20 (#89) —
GET /api/v2/casdefaulted toper_page=20even when no pagination parameters were supplied, so a fresh import of 24 CAs only displayed 20 inCertificate Authorities. The endpoint now returns the full set when no pagination is requested, and continues to honourpage/per_pagewhen they are explicitly provided. -
API key creation UX & no-expiration support (#90) — three regressions in
Account → API Keys:- Newly issued keys are now shown in a dedicated modal with the full key in a
<code>block, an explicit copy button, and a warning that the key won't be shown again. The previous toast disappeared too quickly and its copy button copied the literal stringundefined. - The list view's per-key "copy" affordance now renders the real key prefix (e.g.
ucm_ak_AbC1) instead ofundefined…. Backed by a newkey_prefixcolumn persisted at creation time. Migration026_add_api_key_prefixadds the column on SQLite and PostgreSQL; legacy keys without a stored prefix render an "unavailable (legacy key)" placeholder. - Leaving the expiration field empty now creates a key that never expires, matching the field's helper text. The backend distinguishes "field absent" (keeps the historical 365-day default for API/CLI compatibility) from explicit
null/0/""(no expiration). Validation rejects negative or non-integer values with HTTP 400.
- Newly issued keys are now shown in a dedicated modal with the full key in a
-
Datetime serialisation now consistently UTC across the API (#87) — every
datetime.isoformat()returned by the backend now carries an explicitZsuffix via the newutc_isoformat()helper. Frontend components (audit log viewer, dashboards, certificate detail) consistently render in the user's local timezone without ambiguity. Backend-wide sweep: 84 auto-fixed call sites + 9 manual fixes; full backend test suite (1558 tests) green. -
Windows SSH Host/User CA setup script — clearer diagnostics on
Add-WindowsCapabilityfailure (#75). When OpenSSH Server install fails on a domain-joined / WSUS-managed machine (typical errors0x8024500c WU_E_PT_WMT_MISSING,0x800f0954, WU connectivity codes), the script now prints a labelled diagnostic block: classifies the error, reports the detectedUseWUServer/WUServergroup-policy values, and lists three policy-compliant remediation paths (WSUS approval / FoD-from-WU policy / offline FoD ISO / manual install via Optional Features). The script never modifies WSUS or update policy itself — it explains the problem so the Windows / AD team can fix it.
-
Smart Import duplicate detection (#85) — duplicate detection across
Smart Import,Auto-Renewal, and mTLS enrollment previously matched only onserial_number. Per RFC 5280, serials are only unique per-issuer, so two unrelated certs from different CAs that happened to share a serial were flagged as duplicates (or worse, the wrong cert was returned).- New helper
find_existing_cert_by_identity()uses an indexed(serial_number, issuer DN)pre-filter (RFC 5280) and confirms with the SHA-256 fingerprint of the DER bytes — globally unique, immune to PEM reformatting, 0% false positives. - Applied to:
services/smart_import/validator._check_duplicate_cert(),services/smart_import/importer(CA + leaf cert paths),api/v2/usersmTLS import,api/v2/mtlsmTLS enrollment. - Auto-renewal lookup now also scopes by
carefto disambiguate identical serials issued by different CAs.
- New helper
-
Database Stats panel on PostgreSQL (#83) —
Settings → Databasepreviously showed-for size andNeverforLast Optimizedon PostgreSQL deployments, and the panel never refreshed afterOptimize/Integrity Check.-
get_db_stats()now queriespg_database_size(current_database())on PostgreSQL (was usingos.path.getsizeon the SQLite file path → always0→ frontend rendered-). -
Last OptimizedandLast Integrity Checktimestamps are persisted viaSystemConfig(db_last_optimized,db_last_integrity_check) and surfaced fromget_db_stats()(was hardcodedNeverwith a TODO). -
SettingsPagenow re-runsloadDbStats()afterOptimizeandIntegrity Checksucceed.
-
-
Certificate Activity chart on PostgreSQL (#84) — the dashboard chart always rendered all-zero bars on PostgreSQL.
- Replaced SQLite-only boolean comparisons (
revoked = 0,is_active = 1,ocsp_enabled = 1,cdp_enabled = 1,active = 1) withIS NOT TRUE/= true, which work on both SQLite and PostgreSQL. - Replaced SQLite-only
datetime('now', '+30 days')in the auto-renewal status query with Python-computed bounds passed as parameters. - Without these fixes, the shared
tryblock inget_certificate_trend()raisedoperator does not exist: boolean = integeron PostgreSQL and theexcepthandler returned an empty trend for all three series.
- Replaced SQLite-only boolean comparisons (
-
SMTP OAuth2 (XOAUTH2) for Gmail, Outlook.com & Microsoft 365 (#67) — modern OAuth2 authentication for outbound mail, replacing legacy app-password flows that Microsoft and Google are deprecating.
- Three provider presets (Gmail / Outlook.com / Microsoft 365) auto-fill SMTP host, port, scopes and authorization endpoints. A "Custom OAuth2" option remains for power users who need to register their own Entra/Workspace app.
- Outlook.com personal accounts use a simplified flow that does not require tenant ID / client secret — the user just authorizes UCM with their Microsoft account.
- Microsoft 365 (Entra) flow keeps the full tenant-id / client-id / client-secret form for org admins.
- Per-provider helpers display the exact redirect URI to register, and the UI surfaces clear guidance when a refresh token is missing or unverified.
- Tokens are stored encrypted; refresh is automatic on send.
-
Friendlier Windows SSH CA setup (#75) — the PowerShell script generated by
/ssh/setupno longer closes the prompt before the user can read the output, and ships with a self-elevating one-liner that downloads the script, opens an elevated-NoExitPowerShell, and bypassesExecutionPolicyfor that single run. Works around hosts where.ps1files are blocked by policy.
-
Database maintenance & integrity check on PostgreSQL (#82) —
Optimize DatabaseandCheck Integritypreviously assumed SQLite and failed on PostgreSQL deployments.-
optimize_db()now branches on the configured backend: SQLite runsVACUUM+ANALYZEvia the standard session, PostgreSQL runsVACUUM ANALYZEon a dedicatedAUTOCOMMITconnection (PG forbids VACUUM inside a transaction). -
check_integrity()runsPRAGMA integrity_check/foreign_key_checkon SQLite, and a connectivity +information_schema.tablesprobe on PostgreSQL. - Frontend
handleIntegrityCheckwas reading the response payload at the wrong nesting level, so the success toast never fired even on SQLite — now readsresponse.data.passedconsistently with the rest of the API surface. - Both endpoints continue to use the standard
success_responseenvelope.
-
-
SSO Default Role overrides UCM-managed roles on every login (#81) — until v2.132,
_resolve_rolewas called insideauto_update_usersfor existing users and always fell back todefault_rolewhen norole_mappingmatched. The result: any role change made in the UCM UI (e.g. promoting a user toadmin) was silently reverted to the provider'sdefault_roleon the next SSO login. Two semantically separate concerns — userinfo sync (email/full name) and role sync — were also conflated under a single toggle.-
auto_update_usersnow controls userinfo only (email, full name) and never touches the role. -
default_roleis now strictly a creation-time value. - Role re-sync at login is opt-in via a new
sync_role_on_loginflag (defaultfalse). When enabled, the role is updated only ifrole_mappingresolves the user's external groups to a UCM role; if no mapping matches, the stored role is preserved (nodefault_rolefallback for existing users). - New backend helper
_resolve_role_from_mapping()returnsNonewhen no mapping match is found, making the existing-user code path explicit. - DB migration
023_sso_sync_role_on_loginadds the new column topro_sso_providers(SQLite + PostgreSQL). - SSO settings UI gains a "Sync role from SSO on each login" toggle on LDAP, OAuth2 and SAML provider forms, with explanatory help text.
- Help content updated (in-app help + 9 i18n locales).
- Backend tests added covering the four behaviours: existing user role preserved, mapped sync, no-match no-op, userinfo without role, creation uses
default_role.
-
-
User authentication source tracking — every user record now exposes its origin (
local/ldap/oauth2/saml) plus the originating SSO provider when applicable. Newauth_sourceandsso_provider_idcolumns on theuserstable, populated automatically when SSO provisions an account, and backfilled for existing SSO users by migration024_user_auth_source(SQLite + PostgreSQL) and an in-place fallback in_get_or_create_sso_user. The Users & Groups page gains a colour-coded Source column showing both the auth method and the provider name (e.g.LDAP · Corporate AD). -
Wiki: dedicated SSO-Authentication page covering LDAP / OAuth2 / SAML setup, role mapping,
sync_role_on_login,auth_sourcetracking, and audit events.
-
HSM provider dropdown empty in Create CA wizard (#80) —
CAsPagefiltered HSM providers onis_active && is_connected, fields that don't exist on the/api/v2/hsm/providersresponse. The dropdown was therefore always empty, displaying "No connected HSM provider" even when an HSM was correctly configured and successfully tested. Now uses the actualenabledfield returned by the backend (computed asstatus === 'connected'). Test fixture inCertificatesForms.test.jsxupdated to match the real API contract.
-
PostgreSQL backend on DEB/RPM (#78) —
psycopg2-binaryis now declared inbackend/requirements.txtso the runtime install pulls the driver automatically.Test connectionandSwitch to PostgreSQLno longer fail withNo module named 'psycopg2'on a fresh DEB/RPM install. -
SSO callback crash on role auto-update (#79) — the audit log call after a role change in
api/v2/sso.pyused keyword arguments not accepted byAuditService.log_action()(status='success') and passed the username as a positionalresource_type. Rewritten with the correct kwargs (action='role_change',resource_type='user',resource_name=<username>,username=<username>,success=True). SSO logins that change a user's role no longer raiseTypeError. -
PostgreSQL URL examples harmonized — in-app help, guides and admin docs now show
postgresql://user:pass@host:5432/ucm(consistent with the UI placeholder) instead of mixing inpostgresql+psycopg2://. Both forms remain accepted by the backend validator.
- HSM warning is now provider-aware — the "SoftHSM not detected" banner only shows when SoftHSM is actually the configured provider. Users running OpenBao or a vendor PKCS#11 module no longer see a misleading warning.
- HSM-backed Certificate Authorities (#77.3) — the CA's private signing key can now be generated or stored inside an HSM and never leaves it. The Create CA wizard exposes a Key Storage toggle (Local / HSM); in HSM mode you can generate a new key in the HSM (RSA-2048/3072/4096, EC-P256/P384/P521) or pick an existing unused signing key. All certificate issuance, CRL generation and OCSP responses for the CA are signed by the HSM. PKCS#12, JKS and raw-key export endpoints return HTTP 409 for HSM-backed CAs. CA list and detail views show an "HSM" badge. In-app help and wiki updated in all 9 UI languages.
-
python-dotenvupgraded to 1.2.2 to pick up the latest CVE patches.
- HSM-backed CAs are backed by the existing HSM provider plumbing (PKCS#11, AWS CloudHSM, Azure Key Vault, GCP KMS, OpenBao/Vault Transit). Only OpenBao is exercised in CI; the other providers share the same code path but are not yet end-to-end tested.
- In-place migration of existing local CAs to HSM and HSM key rotation for existing HSM CAs are intentionally out of scope and tracked as separate follow-up items.
-
ACME client and proxy now offer user-controlled SSL verification — both the ACME client (upstream CA directory) and the ACME proxy (target ACME server) expose
verify_ssl/proxy_verify_ssltoggles persisted via/api/v2/acme/client/settings. Default is on; the UI shows a warning banner when disabled. The proxy "Test connection" endpoint now uses the persisted flag (no per-request override) and rejects cloud metadata IPs and loopback targets. -
Outbound HTTP sessions now verify TLS by default —
utils.safe_requests.create_session()defaults toverify_ssl=True. Callers must opt out explicitly when targeting an internal endpoint with a self-signed certificate. -
CSRF exemptions narrowed for SSO and mTLS — previously any
/api/v2/sso/*or/api/v2/mtls/*route was CSRF-exempt. Exemptions are now restricted to the specific public callback/handshake subpaths; admin-write endpoints under those prefixes are now CSRF-protected. -
WebSocket admin endpoints require
admin:systempermission —/api/v2/websocket/clientsand/api/v2/websocket/broadcastnow require admin scope instead of any authenticated session. - Forgot-password endpoint is now rate-limited to mitigate enumeration and brute-force.
-
API keys linked to deactivated users are now rejected —
auth/unified.pychecks theis_activeflag in addition to the key's own validity.
-
Service no longer starts silently when database migrations fail —
run_all_migrations()failures now block startup with a clear error instead of leaving the app half-initialized. -
Database migration runner uses
DATABASE_URLas single source of truth — eliminates the SQLite path mismatch that could arise whenDATABASE_URLandDATABASE_PATHdisagreed. -
Database migration target check is now fail-closed — if the emptiness check on the target database raises, migration aborts instead of continuing. The
_migrationsbookkeeping table DDL is now PostgreSQL-compatible (SERIAL/IDENTITYinstead of SQLiteINTEGER PRIMARY KEY). -
Audit logs for background tasks no longer appear as
anonymous— actions performed without a Flask request context (CRL auto-regeneration, ACME auto-approve, scheduler tasks, startup work) are now correctly labelledsystem,scheduler, oracme.anonymousis reserved for genuinely unauthenticated HTTP requests.
- Service fails to start after upgrading to v2.128 on SQLite installs — the new v2.128 database migrations did not apply on upgrade and the service stayed in a failed state. Fresh installs were not affected.
-
Custom Extended Key Usage (EKU) OIDs when issuing certificates and signing CSRs (RFC 5280 §4.2.1.12) — the Issue Certificate form and the Sign CSR modal now expose an "Extra EKUs" multi-select that combines a dropdown of well-known EKUs (Microsoft RDP
1.3.6.1.4.1.311.54.1.2, smartcard logon1.3.6.1.4.1.311.20.2.2, document signing, IPsec, Kerberos PKINIT, etc. — 18 catalog entries via the newGET /api/v2/eku/knownendpoint) with a free-text input that accepts any well-formed dotted OID. The cert_type's default EKUs (e.g.serverAuthfor server certs) remain locked-in as chips and the extras are merged on top — never replaced. Backend validation (utils/eku_validation.py) enforces a 16-OID cap, the^[0-2](?:\.(?:0|[1-9]\d*)){1,15}$OID regex, and explicitly rejectsanyExtendedKeyUsage(2.5.29.37.0). For CSR signing, if the CSR already carries an EKU extension it is rebuilt with the merged set. Fixes #76. -
Active filter state persisted across reloads — applying a filter on Certificates, CAs, Audit Logs, Templates, Policies, TrustStore, HSM, RBAC, SSH Certificates, SSH CAs, Users/Groups, or User Certificates now saves the live selection to
localStorage(one key per filter, e.g.ucm-filter-certs-status). Reloading the page or navigating away and back instantly restores the same filter, with no flash of unfiltered data — the newusePersistedStatehook reads the value synchronously in the React state initializer. Clearing a filter through the UI also removes the correspondinglocalStorageentry, so empty state stays clean. Works alongside the existing named filter presets (which keep using a separate…-presetskey). Fixes #57. -
Windows quick-install script for SSH CA trust — the SSH CA setup script endpoint now accepts a
?platform=windowsquery parameter and returns a PowerShell (.ps1) script that configures the Windows OpenSSH Server to trust the CA (writes the public key to%ProgramData%\ssh, locks down ACLs, addsTrustedUserCAKeys/HostCertificatedirectives tosshd_config, validates withsshd -T, and restarts thesshdservice). Supports both user and host CAs, includes a-DryRunswitch, and works on the public unauthenticated/ssh/setup/<refid>endpoint too. The SSH CA detail panel now shows two download buttons (Linux/macOS.sh+ Windows.ps1) and two Quick Install one-liners (curl … | bashfor Linux/macOS,iwr … | iexfor Windows). Fixes #75. -
User UI preferences persisted server-side — language, theme family, and theme mode are now saved per-user in the database (
users.preferencesJSON column) instead of only in the browser'slocalStorage. New endpointsGET/PUT /api/v2/account/preferences(whitelist-validated, admin or self) store the preferences, and/api/v2/auth/verifyreturns them so they are applied on every page load. Logging in from a fresh browser, a different device, or after clearing site data now restores the user's chosen language and theme instead of falling back to the browser locale and default theme. Migration022adds the column on both SQLite and PostgreSQL. Fixes #73. -
ACME proxy orders linked to local accounts — proxy order rows now record which local
AcmeAccountinitiated them (FKaccount_idresolved from the client JWK thumbprint). The proxy order list now displays the account email/short id beside each order, and the account detail "Orders" tab now merges local + proxy orders with a "Proxy" badge so operators can see all activity per account in one place. Migration021backfillsaccount_idfor existing proxy orders by joining onacme_accounts.jwk_thumbprint. Fixes #71.
-
ACME renewal storm with Let's Encrypt —
AcmeClientOrder.expires_atwas being set from the ACME order resource'sexpiresfield (RFC 8555 §7.1.3, ~7 days for LE) instead of the issued certificate'snotAfter(typically 90 days). The renewal scheduler then re-issued the same certificate every tick, hitting the LE production rate limits.finalize_ordernow stores the leaf certificate'snotAfter, and migration020backfillsexpires_atfor all already-issued orders. Fixes #74.
-
No more compilation toolchain required at install time —
gccandpython3-dev(DEB) /python3-devel(RPM) have been removed from package dependencies. Previously they were needed to build thetwofishC extension pulled in transitively bypyjks(Java KeyStore export). Investigation confirmedtwofishis only used by pyjks for the BKS UBER keystore format, which UCM never produces — UCM only exports JKS.pyjksis now installed viapip install --no-deps pyjks==20.0.0in the postinst scripts (with its actual runtime depsjavaobj-py3+pycryptodomexlisted inrequirements.txt), keeping the install pure-wheel and ~30 MB lighter on RPM systems.
See also: Home · ACME Support · Database Backend · HSM Support · SSO Authentication · Kubernetes / cert-manager