Skip to content

Release Notes v2.128 to v2.140

NeySlim edited this page Apr 27, 2026 · 1 revision

Release Notes — v2.128 → v2.140

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.


[2.140] - 2026-04-27

Fixed

  • Certificate SAN database columns now derived from the final SAN list (#94). When a CN is auto-promoted to an rfc822Name SAN at issuance, the san_email / san_dns / san_ip / san_uri columns are now written from the canonical SAN list instead of the raw form payload, so DB queries match the X.509 extension. Migration 027_backfill_san_email re-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_insert listeners on the Certificate and CA models that immediately materialize .crt / .key files under data/certs/ and data/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.

[2.139] - 2026-04-27

Added

  • 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 / hmac pairs; clients (cert-manager, certbot, acme.sh) bind their account on newAccount via 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-challenge TXT 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_ips SystemConfig (default true). HTTP-01 and TLS-ALPN-01 validation now works out of the box for RFC1918, loopback, .lan / .local / .corp targets — 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.

Changed

  • 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:acme permission added to the operator role to match write:acme.
  • ACME backup/restore parity. acme_eab_credentials is now exported and restored alongside acme_accounts; full account fields (contact, status, terms-of-service, external-account-binding metadata) are now round-tripped end-to-end.

Fixed

  • backend/services/ssh_cas.py — converted f-strings containing escape sequences to raw f-strings to silence Python SyntaxWarning: invalid escape sequence on 3.12+.

[2.138] - 2026-04-25

Fixed

  • CAs page silently dropped CAs beyond the first 20 (#89)GET /api/v2/cas defaulted to per_page=20 even when no pagination parameters were supplied, so a fresh import of 24 CAs only displayed 20 in Certificate Authorities. The endpoint now returns the full set when no pagination is requested, and continues to honour page / per_page when 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 string undefined.
    • The list view's per-key "copy" affordance now renders the real key prefix (e.g. ucm_ak_AbC1) instead of undefined…. Backed by a new key_prefix column persisted at creation time. Migration 026_add_api_key_prefix adds 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.

[2.137] - 2026-04-24

Fixed

  • Datetime serialisation now consistently UTC across the API (#87) — every datetime.isoformat() returned by the backend now carries an explicit Z suffix via the new utc_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-WindowsCapability failure (#75). When OpenSSH Server install fails on a domain-joined / WSUS-managed machine (typical errors 0x8024500c WU_E_PT_WMT_MISSING, 0x800f0954, WU connectivity codes), the script now prints a labelled diagnostic block: classifies the error, reports the detected UseWUServer / WUServer group-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.

[2.136] - 2026-04-24

Fixed

  • Smart Import duplicate detection (#85) — duplicate detection across Smart Import, Auto-Renewal, and mTLS enrollment previously matched only on serial_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/users mTLS import, api/v2/mtls mTLS enrollment.
    • Auto-renewal lookup now also scopes by caref to disambiguate identical serials issued by different CAs.

[2.135] - 2026-04-23

Fixed

  • Database Stats panel on PostgreSQL (#83)Settings → Database previously showed - for size and Never for Last Optimized on PostgreSQL deployments, and the panel never refreshed after Optimize / Integrity Check.
    • get_db_stats() now queries pg_database_size(current_database()) on PostgreSQL (was using os.path.getsize on the SQLite file path → always 0 → frontend rendered -).
    • Last Optimized and Last Integrity Check timestamps are persisted via SystemConfig (db_last_optimized, db_last_integrity_check) and surfaced from get_db_stats() (was hardcoded Never with a TODO).
    • SettingsPage now re-runs loadDbStats() after Optimize and Integrity Check succeed.
  • 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) with IS 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 try block in get_certificate_trend() raised operator does not exist: boolean = integer on PostgreSQL and the except handler returned an empty trend for all three series.

[2.134] - 2026-04-23

Added

  • 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/setup no 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 -NoExit PowerShell, and bypasses ExecutionPolicy for that single run. Works around hosts where .ps1 files are blocked by policy.

Fixed

  • Database maintenance & integrity check on PostgreSQL (#82)Optimize Database and Check Integrity previously assumed SQLite and failed on PostgreSQL deployments.
    • optimize_db() now branches on the configured backend: SQLite runs VACUUM + ANALYZE via the standard session, PostgreSQL runs VACUUM ANALYZE on a dedicated AUTOCOMMIT connection (PG forbids VACUUM inside a transaction).
    • check_integrity() runs PRAGMA integrity_check / foreign_key_check on SQLite, and a connectivity + information_schema.tables probe on PostgreSQL.
    • Frontend handleIntegrityCheck was reading the response payload at the wrong nesting level, so the success toast never fired even on SQLite — now reads response.data.passed consistently with the rest of the API surface.
    • Both endpoints continue to use the standard success_response envelope.

[2.133] - 2026-04-23

Fixed

  • SSO Default Role overrides UCM-managed roles on every login (#81) — until v2.132, _resolve_role was called inside auto_update_users for existing users and always fell back to default_role when no role_mapping matched. The result: any role change made in the UCM UI (e.g. promoting a user to admin) was silently reverted to the provider's default_role on 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_users now controls userinfo only (email, full name) and never touches the role.
    • default_role is now strictly a creation-time value.
    • Role re-sync at login is opt-in via a new sync_role_on_login flag (default false). When enabled, the role is updated only if role_mapping resolves the user's external groups to a UCM role; if no mapping matches, the stored role is preserved (no default_role fallback for existing users).
    • New backend helper _resolve_role_from_mapping() returns None when no mapping match is found, making the existing-user code path explicit.
    • DB migration 023_sso_sync_role_on_login adds the new column to pro_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.

Added

  • User authentication source tracking — every user record now exposes its origin (local / ldap / oauth2 / saml) plus the originating SSO provider when applicable. New auth_source and sso_provider_id columns on the users table, populated automatically when SSO provisions an account, and backfilled for existing SSO users by migration 024_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_source tracking, and audit events.

[2.132] - 2026-04-23

Fixed

  • HSM provider dropdown empty in Create CA wizard (#80)CAsPage filtered HSM providers on is_active && is_connected, fields that don't exist on the /api/v2/hsm/providers response. The dropdown was therefore always empty, displaying "No connected HSM provider" even when an HSM was correctly configured and successfully tested. Now uses the actual enabled field returned by the backend (computed as status === 'connected'). Test fixture in CertificatesForms.test.jsx updated to match the real API contract.

[2.131] - 2026-04-22

Fixed

  • PostgreSQL backend on DEB/RPM (#78)psycopg2-binary is now declared in backend/requirements.txt so the runtime install pulls the driver automatically. Test connection and Switch to PostgreSQL no longer fail with No 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.py used keyword arguments not accepted by AuditService.log_action() (status='success') and passed the username as a positional resource_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 raise TypeError.
  • 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 in postgresql+psycopg2://. Both forms remain accepted by the backend validator.

Also in this release (carried over from cancelled v2.131-rc)

  • 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.

[2.130] - 2026-04-22

Added

  • 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.

Security

  • python-dotenv upgraded to 1.2.2 to pick up the latest CVE patches.

Notes

  • 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.

[2.129] - 2026-04-21

Security

  • 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_ssl toggles 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 defaultutils.safe_requests.create_session() defaults to verify_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:system permission/api/v2/websocket/clients and /api/v2/websocket/broadcast now 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 rejectedauth/unified.py checks the is_active flag in addition to the key's own validity.

Fixed

  • Service no longer starts silently when database migrations failrun_all_migrations() failures now block startup with a clear error instead of leaving the app half-initialized.
  • Database migration runner uses DATABASE_URL as single source of truth — eliminates the SQLite path mismatch that could arise when DATABASE_URL and DATABASE_PATH disagreed.
  • Database migration target check is now fail-closed — if the emptiness check on the target database raises, migration aborts instead of continuing. The _migrations bookkeeping table DDL is now PostgreSQL-compatible (SERIAL/IDENTITY instead of SQLite INTEGER 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 labelled system, scheduler, or acme. anonymous is reserved for genuinely unauthenticated HTTP requests.

[2.128.1] - 2026-04-21

Fixed

  • 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.

[2.128] - 2026-04-21

Added

  • 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 logon 1.3.6.1.4.1.311.20.2.2, document signing, IPsec, Kerberos PKINIT, etc. — 18 catalog entries via the new GET /api/v2/eku/known endpoint) with a free-text input that accepts any well-formed dotted OID. The cert_type's default EKUs (e.g. serverAuth for 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 rejects anyExtendedKeyUsage (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 new usePersistedState hook reads the value synchronously in the React state initializer. Clearing a filter through the UI also removes the corresponding localStorage entry, so empty state stays clean. Works alongside the existing named filter presets (which keep using a separate …-presets key). Fixes #57.
  • Windows quick-install script for SSH CA trust — the SSH CA setup script endpoint now accepts a ?platform=windows query 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, adds TrustedUserCAKeys/HostCertificate directives to sshd_config, validates with sshd -T, and restarts the sshd service). Supports both user and host CAs, includes a -DryRun switch, 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 … | bash for Linux/macOS, iwr … | iex for Windows). Fixes #75.
  • User UI preferences persisted server-side — language, theme family, and theme mode are now saved per-user in the database (users.preferences JSON column) instead of only in the browser's localStorage. New endpoints GET/PUT /api/v2/account/preferences (whitelist-validated, admin or self) store the preferences, and /api/v2/auth/verify returns 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. Migration 022 adds the column on both SQLite and PostgreSQL. Fixes #73.
  • ACME proxy orders linked to local accounts — proxy order rows now record which local AcmeAccount initiated them (FK account_id resolved 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. Migration 021 backfills account_id for existing proxy orders by joining on acme_accounts.jwk_thumbprint. Fixes #71.

Fixed

  • ACME renewal storm with Let's EncryptAcmeClientOrder.expires_at was being set from the ACME order resource's expires field (RFC 8555 §7.1.3, ~7 days for LE) instead of the issued certificate's notAfter (typically 90 days). The renewal scheduler then re-issued the same certificate every tick, hitting the LE production rate limits. finalize_order now stores the leaf certificate's notAfter, and migration 020 backfills expires_at for all already-issued orders. Fixes #74.

Changed

  • No more compilation toolchain required at install timegcc and python3-dev (DEB) / python3-devel (RPM) have been removed from package dependencies. Previously they were needed to build the twofish C extension pulled in transitively by pyjks (Java KeyStore export). Investigation confirmed twofish is only used by pyjks for the BKS UBER keystore format, which UCM never produces — UCM only exports JKS. pyjks is now installed via pip install --no-deps pyjks==20.0.0 in the postinst scripts (with its actual runtime deps javaobj-py3 + pycryptodomex listed in requirements.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

Clone this wiki locally