Skip to content

Commit 7178620

Browse files
docs(v1.9.0): correct upgrade notes on built-in role re-seed + backfill 1.8.8-1.8.10 release notes
Found during a v1.8.10 to v1.9.0 upgrade drill on a populated database (schema v9 to v10) before releasing. Documentation only, no code change. 1. The v1.9.0 upgrade notes said "custom roles need no changes", which reads as "role data is untouched". It is not: because the SCHEMA_VERSION bump re-runs the whole idempotent sequence, update_system_roles_to_enterprise_rbac() issues an unconditional UPDATE roles SET ... permissions = <defaults> for the four BUILT-IN roles. In the drill an `operator` role that had been narrowed by removing apply.execute and config.bulk_import came back with both restored (57 to 59 permissions). Operator-created roles are NOT affected; the re-seed matches the four built-in names only. This is pre-existing behaviour of every SCHEMA_VERSION bump and is documented as intentional in migrations.py, so it is not introduced by the CSR feature. The v1.7.0 upgrade notes carried this caveat and it was not carried forward. Restored, with an export and re-apply procedure. Also clarified why the admin password is safe: the default-user seeding is guarded by an existence check ("safer than ON CONFLICT"), not an upsert, so an operator-changed password survives. 2. README release notes jumped from v1.8.7 straight to v1.9.0 because v1.8.8, v1.8.9 and v1.8.10 were never backfilled. Added all three.
1 parent 33e3e8e commit 7178620

2 files changed

Lines changed: 25 additions & 3 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2475,6 +2475,9 @@ Developed with ❤️ for the HAProxy community
24752475
## Release Notes
24762476

24772477
- **v1.9.0** (2026-08-04) — **CSR creation** (in-app key + CSR generation and signed-certificate import): a new **CSR tab** on the SSL Certificates page generates a private key and Certificate Signing Request server-side (RSA 2048/4096 or ECDSA P-256/P-384; full subject — O/OU/L/ST/C/email — plus DNS SANs with wildcard support), for certificates signed by an **external or corporate CA**. The operator downloads/copies the CSR PEM, has it signed, then imports the signed certificate (+ optional chain): the backend verifies the certificate against the stored key (hard gate), rejects expired certs, warns on SAN drift, and creates a normal SSL certificate entry (source `CSR`) that flows through the standard **PENDING → Apply Management → agent pull** pipeline. The private key **never leaves the server** — no CSR endpoint returns it, and after import the CSR row's key copy is destroyed (the key then lives only on the certificate, like every other key). Additive schema change: one new table `ssl_csrs` (SCHEMA_VERSION 9 → 10, auto-migrated, no existing table altered); key generation runs off the event loop and is rate-limited per user; existing `ssl.*` permissions govern all new endpoints. No agent or rendered-config changes.
2478+
- **v1.8.10** (2026-07-20) — **Security hardening** (GHSA-7rhv-c5pc-69r8, GHSA-3p5c-m5m4-mjpx, GHSA-3vh4): three advisory classes remediated, backend-only, no agent changes. (1) **RCE**: the agent script-template read/write endpoints now require the `agents.version` permission on top of authentication — a poisoned template is executed as root on every HAProxy node, so authentication alone was insufficient. (2) **Missing authentication**: operator/UI endpoints that were served without a JWT (dashboard stats, pool/cluster listings, agent inventory, WAF rules, config validate/optimize, SSL config-versions, health deep/agents/clusters) are now gated by a `require_authenticated_user` dependency, and agent data-plane endpoints that treated the `X-API-Key` header as *optional* (heartbeat, config, ssl-certificates, upgrade-status, pending-requests) now hard-reject a missing key. In every case the auth check was moved **ahead of** the handler's `try:` block so a 401 can no longer be rewritten into a 500 by the generic exception handler. (3) **SSRF**: a new `utils/ssrf_guard.py` (https-only, IPv4-pinned connector, all resolved addresses must be public, no redirects) protects the ACME directory fetch, the signed-request target and the ACME connection test, which accept operator- or DB-supplied URLs; the connection test also stopped reflecting arbitrary upstream JSON. Frontend dependency advisories patched in the same release. No schema, API-shape or rendered-config changes.
2479+
- **v1.8.9** (2026-07-13) — **ACL `-f` pattern-file support** (Issue #38 follow-up): ACL definitions that reference a host-side pattern file (`acl … -f /etc/haproxy/lists/blocked.lst`) are accepted on import and edit instead of being rejected. The referenced file lives on the HAProxy node and cannot be validated from the manager, so the manager emits an **advisory warning** rather than a hard rejection and lets the agent's `haproxy -c` check be the fail-safe gate (a broken reference fails validation on the node and the previous config is restored). Consistent with the SPOE handling introduced in v1.8.8.
2480+
- **v1.8.8** (2026-07-10) — **SPOE filter and frontend `log-format` preserved on import/edit** (Issue #38): importing an existing `haproxy.cfg` or editing a frontend silently dropped `filter spoe …` directives and custom `log-format` lines, so the next Apply pushed a config that had lost them. Both are now round-tripped through import and edit. As with `-f` pattern files, the SPOE engine config is a host-side file the manager cannot read, so it is preserved verbatim and reported as an advisory rather than validated centrally.
24782481
- **v1.8.7** (2026-07-09) — **Version reporting single-source fix**: the version shown in the UI (backend-sourced via `/api/version`) could lag behind the real release. The canonical version lived in the repo-root `version.json`, but the backend image is built from the `./backend` context, so that file did not reach the container in every pipeline; the backend then fell back to a hardcoded constant in `main.py` that had to be bumped by hand and had drifted (it reported 1.8.4 after 1.8.5/1.8.6 shipped). The version now lives in a single file, `backend/version.json`, baked into every image automatically, and `main.py` no longer carries a real version literal (its fallback is a neutral "unknown"). A new test enforces that the version stays single-source and cannot drift. No functional or API change.
24792482
- **v1.8.6** (2026-07-06) — **Performance: opt-in API workers + heartbeat micro-optimization** (Issue #35 follow-up): the backend container can now run multiple uvicorn worker processes via the new `UVICORN_WORKERS` environment variable (default **1** — behavior unchanged unless you opt in), letting the API use all cores on multi-core hosts; background tasks were already multi-replica safe, as exercised by the Kubernetes HPA deployment. The agent heartbeat handler now reads the agent's `status`/`version`/`upgrade_status` in one query instead of three (one round-trip per heartbeat, per agent, every 30s). Added a *Performance Tuning* section to the README (worker/replica scaling and how to use the `X-Response-Time` header and `Slow request detected` logs to pinpoint slow endpoints). Zero-risk release: no schema, API, or agent changes; defaults preserve existing behavior exactly.
24802483
- **v1.8.5** (2026-07-03) — **ACME completion-task SQL fix** (Issue #35 follow-up): the background order-completion task (`complete_pending_acme_orders`, runs every 60s) died on **every cycle** with `syntax error at or near ")"` — an extra closing parenthesis introduced in v1.8.0's bounded DNS-01 retry claim query. Because that query is the task's first database call, **no background ACME work ran at all from v1.8.0 through v1.8.4**: orders were never claimed for finalize/download, the DNS-01 TXT record was never published (so DNS-01 with an automated provider such as Cloudflare could never validate), Site Wizard staged orders never left `wizard_staged`, and DNS-01 retry/TXT-cleanup never executed. The stray parenthesis is removed and a regression test now scans all ACME modules' SQL for unbalanced parentheses (the unit suite mocks the database, which is why a raw-SQL syntax error could slip through). One-line backend query fix; no schema, API, or agent changes — fully backward compatible.

UPGRADE_GUIDE.md

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,28 @@ clusters/agents until you create a CSR:
66
- **Schema:** `SCHEMA_VERSION` bumps to `10`, so on first start the (idempotent)
77
migration sequence re-runs once and adds **one new table** (`ssl_csrs`) plus its
88
indexes. **No existing table is altered**, existing rows are untouched, and the
9-
**admin password is not reset**. No new permission strings are introduced — all CSR
10-
endpoints are governed by the existing `ssl.create` / `ssl.read` / `ssl.delete`
11-
permissions, so custom roles need no changes.
9+
**admin password is not reset** (the default-user seeding is guarded by an
10+
existence check, not an upsert). No new permission strings are introduced — all
11+
CSR endpoints are governed by the existing `ssl.create` / `ssl.read` /
12+
`ssl.delete` permissions.
13+
- **⚠️ Built-in roles are re-seeded to their defaults (pre-existing behaviour of
14+
every `SCHEMA_VERSION` bump — verified in a v1.8.10 → v1.9.0 upgrade drill).**
15+
Because the version gate re-runs the whole sequence, `update_system_roles_to_enterprise_rbac()`
16+
issues an unconditional `UPDATE roles SET … permissions = <defaults> WHERE name = …`
17+
for the four **built-in** roles (`super_admin`, `operator`, `security_admin`,
18+
`viewer`). **Any customization you made to a built-in role is reverted.** In the
19+
drill, an `operator` role that had been narrowed by removing `apply.execute` and
20+
`config.bulk_import` came back with both restored (57 → 59 permissions).
21+
- **Roles you created yourself are NOT affected** — the re-seed matches on the four
22+
built-in names only.
23+
- This is not new in v1.9.0: it happens on every release that bumps
24+
`SCHEMA_VERSION` (v1.7.0, v1.8.0, v1.8.8 …). It is documented as intentional at
25+
`backend/database/migrations.py` (the "BUMP THIS … OR seeded/role data" note) —
26+
the migration is treated as the authority on built-in-role contents.
27+
- **If you have hardened a built-in role, do this:** export it before upgrading
28+
(`GET /api/roles`), then re-apply your changes after the first start
29+
(`PUT /api/roles/{id}`) — or, preferably, move your customization into a
30+
purpose-made custom role, which survives every upgrade.
1231
- **Key storage:** CSR private keys are stored in the database like every other key
1332
in the system (`ssl_certificates.private_key_content` and the ACME order keys).
1433
The key is never returned by any CSR API endpoint, and after a successful import

0 commit comments

Comments
 (0)