Skip to content

v2.144

Choose a tag to compare

@github-actions github-actions released this 03 May 11:50
· 191 commits to main since this release

What's Changed

Added

  • utils/key_codec.pyload_pem_bytes(prv, *, context) / store_pem_bytes(pem) helpers that consolidate the previously duplicated base64.b64decode(decrypt_private_key(model.prv)) pattern across 26 sites in api/v2/* and services/*. Errors now surface a caller-supplied context ("CA 42", "certificate 17") instead of an opaque binascii.Error when a stored .prv is malformed or was encrypted with a different KEY_ENCRYPTION_KEY.
  • utils/db_transaction.commit_or_rollback() — boolean-returning service-layer counterpart to safe_commit() (which is Flask-response-returning). Replaces 10 bare db.session.commit() calls in auth/unified.py, services/mtls_auth_service.py, services/webauthn_service.py that previously could leak partial transactions on integrity errors.
  • security/encryption.encrypt_text() / decrypt_text() — text-oriented helpers (PEM, JSON blobs, plain strings) that share the same wire format as encrypt_string() but never confuse the caller about the input contract. The mixed encrypt() (expects base64) vs encrypt_string() (expects text) split caused #105.
  • Generic release toolingscripts/smoke_release.py (auth/CDP/OCSP/EST/health probe), scripts/release_publish.sh (tag + GitHub release publish), scripts/wiki_release_notes.py (changelog → wiki page generator). Lab-specific hostnames removed; everything is parameterised via UCM_BASE env var.
  • CI workflows.github/workflows/tests.yml runs the backend suite against both SQLite and PostgreSQL on every push (closes the gap that let #103 ship). .github/workflows/release-smoke.yml runs smoke_release.py against the published artefacts after every v* tag.
  • Pytest postgres marker — opt-in marker for tests that require a live PostgreSQL backend; skipped by default locally, always run in CI.

Fixed

  • Silent except Exception: pass blocks in critical auth/security paths now log with exc_info=True. Specifically: auth/unified.py (4 sites: lockout config read, account-locked notification, login/logout WebSocket broadcasts, SMTP probe), api/v2/auth.py (7 sites: password policy import, password reset audit, etc.), security/csrf.py (CSRF token extraction failures), security/encryption.py, config/https_manager.py, services/audit/core.py, services/email_service.py, services/syslog_service.py, utils/backup_codes.py. These were not bugs in themselves but made post-mortem debugging of auth failures effectively impossible.
  • Latent #105-class regressions — 4 additional sites that round-tripped PEM through encrypt()/decrypt() were migrated to encrypt_text()/decrypt_text().
  • 10 bare db.session.commit() sites in auth/mTLS/WebAuthn paths now wrap in commit_or_rollback() and rollback cleanly on IntegrityError.

Changed

  • 26-site refactor to utils/key_codec.load_pem_bytes(). Behaviour-preserving (asserted by TestEquivalenceWithLegacyPattern test class); reduces import footprint (single utils.key_codec import vs base64 + security.encryption).

Tests

  • tests/test_key_codec.py (8 tests) — round-trip with/without KEY_ENCRYPTION_KEY, error messages with caller context, byte-for-byte equivalence with the legacy inline pattern.
  • tests/test_db_transaction.py (5 tests) — commit_or_rollback() returns False + rolls back on IntegrityError, returns True on success, no double-rollback when called twice.
  • tests/test_pem_encryption_helpers.py, tests/test_acme_proxy_key_encrypted.py, tests/test_key_encryption_pem_passthrough.py, tests/test_migration_runner_pg.py — regression coverage for #103/#104/#105 to prevent re-introduction.

Internal

  • Backend suite: 1645 passed / 1 skipped (was 1632 / 1 in v2.143).
  • Frontend suite: 461 passed.

📜 Recent release history (last 2 versions)

[2.143] - 2026-05-03

Fixed

  • PostgreSQL migration runner crashed on startup when applying any pending migration written for the legacy Engine interface. _run_pending_pg() now opens a single transactional Connection via engine.begin() and passes it to mod.upgrade(conn), matching the SQLite path and the migration module signatures (#103, #104). Without this fix, fresh PostgreSQL deployments couldn't boot past first start, and existing PG instances couldn't apply any future migration.
  • ACME proxy account private key was stored in plaintext in system_config. It is now encrypted at rest with the application key via encrypt_private_key() / decrypt_private_key(), and existing plaintext keys are migrated transparently on first read (#105).
  • KeyEncryption.decrypt() no longer raises binascii.Error on PEM-formatted input that was never encrypted in the first place. The probe now isolates base64 detection from Fernet decryption so legacy plaintext keys round-trip cleanly through the new ACME proxy decrypt path.

Changed

  • Cross-target release validation now covers PostgreSQL in addition to SQLite for every supported package (DEB, RPM, Docker). The PostgreSQL backend is now part of the mandatory pre-release smoke matrix because the #103 regression only manifested on PostgreSQL and would have shipped silently against a SQLite-only matrix.

[2.142] - 2026-05-02

Security

  • EST /cacerts, /simpleenroll, /simplereenroll, /serverkeygen, /csrattrs now enforce est_enabled on every request and short-circuit with 503 EST disabled instead of falling through to the SPA. The /serverkeygen body is also size-capped and stricter about content negotiation.
  • EST and SCEP mTLS client certificates are only honoured when the request comes from a trusted reverse proxy (security.trusted_proxies). Direct hits without TLS termination by UCM no longer accept proxy-injected client cert headers.
  • mTLS login route gated behind trusted-proxy check. Same protection as EST/SCEP — header-based mTLS is rejected unless the request originates from a trusted proxy.
  • 2FA backup codes are now hashed at rest (Argon2id) and consumed atomically; plaintext codes are returned only at generation time and never stored.
  • Approval quorum is race-safe and idempotent. Concurrent approvals on the same request can no longer over-approve; double-submits are deduplicated.
  • On-demand CRL generation is serialised per-CA with a per-CA lock and 503 Retry-After: 5 under contention — closes a CPU/IO DoS vector when many clients hit /cdp/<ca>.crl simultaneously.
  • Outbound webhooks revalidate the resolved IP at delivery time (DNS-rebinding window closed) and reject cloud-metadata IPs (169.254.169.254, GCP/Azure/Alibaba equivalents) and loopback. RFC1918 / .lan / .local targets remain allowed by design (UCM is on-prem).
  • SSO/IdP, ACME proxy and webhook URL fields all share the same SSRF helper (validate_url_not_cloud_metadata) — cloud metadata is blocked everywhere.
  • CSV bulk user-import capped at 5 MB / 10 000 rows with 413 on overflow.
  • Runtime HSM pip install disabled by default, returns 403 with a hint to set UCM_ALLOW_RUNTIME_PIP=1 or install the dependency via the system package manager.
  • SCEP CSRs no longer copy arbitrary KU/EKU bits — only a whitelist (digitalSignature, keyEncipherment, serverAuth, clientAuth) is honoured.
  • SCEP RFC 8894 P0/P1/P2 hardening — stricter PKCS#7 parsing, transaction-ID validation, signed/encrypted response envelope checks; iOS/macOS enrollment fixes (#102).
  • ACME account private keys encrypted at rest with the application key.
  • Password change endpoint ignores client-supplied force_change (only operators can clear that flag).
  • CSRF token entropy increased; password hash algorithm tightened; database migration identifiers validated against an allow-list.
  • ProxyFix is opt-in via security.trusted_proxies — prevents unauthenticated X-Forwarded-For spoofing on direct deployments.
  • Filesystem session directory is now created/enforced at mode 0o700 and the application refuses to boot if it has group/world-readable bits.
  • EST audit lines use the trusted-proxy-aware client IP instead of the raw socket address.

Added

  • utils/trusted_proxy.py — shared is_request_from_trusted_proxy() / client_ip() / reject_untrusted_proxy_headers() helpers used by EST, SCEP, mTLS login and audit.
  • utils/ssrf_protection.py — single source of truth for validate_url_not_cloud_metadata and validate_host_not_cloud_metadata, used by webhooks, SSO, ACME proxy, OPNsense import.
  • utils/safe_commit.py, utils/require_json_body, utils/parse_request_pagination, utils/safe_call, utils/audit_event — small composable helpers applied across api/v2/* to remove boilerplate and silence intermittent rollback bugs.
  • useCRUDPage frontend hook covering 4 list/create/edit pages.

Changed

  • Massive backend modularisation. system.py (1556 l), certificates.py (2220 l), cas.py (1245 l), ssh_cas.py (1607 l), database_admin (817 l), discovery_service, pdf_generator, scep_service (981 l), acme_service (1456 l → 7 mixins ≤350 l), trust_store (1487 l), ca_service (788 l), restore_mixin, notification_service, audit_service, crl_service, ssh_cert_service, msca_service, account.py, acme.py, tools.py, acme_client.py, users.py, settings.py, opnsense_import and models/__init__.py were split into focused submodules. Behaviour is unchanged; module size, test isolation and review surface improve.
  • Frontend modularisation. CAsPage, CertificatesPage, DiscoveryPage, ACMEPage, SettingsPage and SsoProviderForm split into per-section sub-components under pages/<feature>/.
  • All api/v2/* db.session.commit() calls now go through safe_commit() — consistent rollback + error logging on every write path.

Fixed

  • PKCS12/PFX export now honours the include_chain flag (#100). Previously the CA chain was always included, regardless of the request.
  • Dashboard chart cards no longer overflow the grid and System Health gained an internal scrollbar (#99).
  • iOS/macOS SCEP enrollment regressions (#102).

Internal

  • ~20 test files de-duplicated against conftest.py; pre-commit i18n + 461 frontend + 1613 backend tests gate every commit.
  • RC validated end-to-end on Debian (pve:8445), Fedora (fedor:8443) and Docker (pve:8444): smoke API 8/8 and Playwright use-cases 10/10 on all three targets.

Full history: CHANGELOG.md


Installation

Docker (Recommended)

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

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

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

Debian/Ubuntu

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

Fedora/RHEL

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

Silent/Automated Install

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

Default Credentials

  • Username: admin
  • Password: changeme123

Change the password immediately after first login!

Documentation