You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Prove dotCMS can run the OpenSearch migration against an OS 3.x cluster reached through a restricted, non-admin user (the managed-cloud setup validated in spike #35922) — not just an
open, security-disabled cluster. Covers provisioning the user, pointing dotCMS at it, and confirming
index operations work through the limited role.
Glossary (quick)
ES (old engine): Elasticsearch — http://localhost:9200, plain HTTP, no auth.
OS (new engine): OpenSearch 3.x — https://localhost:9201, TLS + basic auth.
.os tag: new-engine index names end in .os (e.g. cluster_dotcms….working_….os).
Setup — limited-user stack + the provisioning script
Stack:docker/docker-compose-examples/os-migration/docker-compose.limited-user.yml (OS 3.x with
the security plugin ON, provisioned with the non-admin dotcms-es-user). The docker/docker-compose-examples/single-node-os-migration/ variant runs OS 1.x + OS 3.x, provisioned
the same way.
docker compose -f docker/docker-compose-examples/os-migration/docker-compose.limited-user.yml up -d
Users on OS 3.x (port 9201, HTTPS):
User
Password
Role
admin
Dev!Search3-Kx9mP-2026
cluster admin
dotcms-es-user
Dev!dotcms-EsUser-2026
dotcms-role (limited)
Provisioning script opensearch.py — runs once via the opensearch-provision service; creates
per customer an internal user <customer>-es-user, action groups, a role <customer>-role, and the
role mapping. Run it manually:
# Re-run the bundled provisioner against the running stack (idempotent):
docker compose -f docker/docker-compose-examples/os-migration/docker-compose.limited-user.yml run --rm opensearch-provision
# Or standalone against any reachable cluster:
./opensearch.py --admin-user admin --admin-pass 'Dev!Search3-Kx9mP-2026' \
--password 'Dev!dotcms-EsUser-2026' --customer dotcms --host localhost --port 9201
The dotcms-role grants:
Index (pattern cluster_<customer>*): indices_all, indices_monitor
Cluster:cluster:monitor/health, cluster:monitor/state, cluster:monitor/nodes/stats, indices:data/write/bulk, indices:data/read/scroll, indices:data/read/scroll/clear, and cluster:monitor/main (GET / version probe — the spike Spike: Validate dotCMS OpenSearch client requests against a non-admin OS 3.x user #35922 fix; without it dotCMS misreads
OS as unreachable and silently falls back to ES)
⚠️Cluster-id gotcha: the role only grants index permissions on names matching cluster_<customer>*.
dotCMS names indexes cluster_<DOT_DOTCMS_CLUSTER_ID>.*, so DOT_DOTCMS_CLUSTER_ID must start with
the customer name (e.g. dotcms-os-migration) or the limited user gets 403 on its own indexes.
dotCMS config:
DOT_ES_ENDPOINTS=http://<host>:9200
DOT_OS_ENDPOINTS=https://<host>:9201
DOT_OS_AUTH_TYPE=BASIC
DOT_OS_AUTH_BASIC_USER=dotcms-es-user
DOT_OS_AUTH_BASIC_PASSWORD=Dev!dotcms-EsUser-2026
DOT_OS_TLS_TRUST_SELF_SIGNED=true
DOT_FEATURE_FLAG_OPEN_SEARCH_PHASE=1
DOT_DOTCMS_CLUSTER_ID=dotcms-os-migration # MUST start with the customer name
Test Cases
TC-053 — Provision the limited user and verify the role/permissions(Risk: Medium)
Launch the limited-user stack; let opensearch-provision finish.
List users: curl -sk https://localhost:9201/_plugins/_security/api/internalusers?pretty -u admin:'Dev!Search3-Kx9mP-2026' → dotcms-es-user present.
Expected:dotcms-es-user + dotcms-role exist with the listed permissions (incl. cluster:monitor/main); GET / as the limited user returns 200.
TC-054 — dotCMS starts against OS as the limited user; migration NOT disabled(Risk: High)
Stack up; dotCMS configured as in Setup (Phase 1, cluster id starts with dotcms).
Start dotCMS; watch the log; confirm the phase (Admin → System → Index, or log).
Expected: dotCMS starts and runs normally; migration NOT auto-disabled (no "OpenSearch cluster is not reachable" / phase-reset lines); log shows OS version check passed: 3.x; both ES and .os indexes listed → Phase 1.
TC-055 — Dual-write works through the limited role (Phase 1)(Risk: High)
(TC-054 passed.) Create and publish a content item; note its identifier.
As the limited user, confirm it in OS (index ends in .os): curl -sk "https://localhost:9201/<cluster_dotcms….working_….os>/_doc/<id>" -u dotcms-es-user:'Dev!dotcms-EsUser-2026'.
Expected: document present in the OS working index (200/found); no 403 in the dotCMS log for the OS write.
TC-056 — Cluster-id mismatch → limited user gets 403 on its own indexes (gotcha)(Risk: Medium)
Configure dotCMS with a cluster id NOT starting with the customer name, e.g. DOT_DOTCMS_CLUSTER_ID=acme-test. Start in Phase 1.
Publish content; watch the log and try an OS write/read as the limited user.
Expected: OS ops on cluster_acme-test.* fail with 403 (outside cluster_dotcms*); dotCMS logs the OS write failure (fire-and-forget in Phase 1 — ES unaffected). Fix: cluster id must start with dotcms (or re-provision with matching --customer) → 403 gone.
TC-057 — Read/search works through the limited user(Risk: Medium)
Content dual-written (TC-055). As the limited user: curl -sk "https://localhost:9201/<…os>/_search?q=*:*" -u dotcms-es-user:'Dev!dotcms-EsUser-2026'.
(Phase 2) run a dotCMS content search and confirm results.
Expected: search returns results (no 403); scroll-based reads succeed.
TC-058 — (Regression) Without cluster:monitor/main, dotCMS misreads OS as unreachable(Risk: Medium)
Remove cluster:monitor/main from dotcms-role (security API, or re-provision without it).
Confirm GET / as the limited user now returns 403.
Restart dotCMS in Phase 1 against this user; watch the log.
Expected: version probe fails → migration auto-disabled (the "OpenSearch cluster is not reachable" + phase-reset lines), dotCMS falls back to ES. Restoring cluster:monitor/main (re-run the provisioner) → TC-054 passes again.
Acceptance Criteria
TC-053: dotcms-es-user/dotcms-role provisioned with expected perms (incl. cluster:monitor/main); GET / as limited user = 200.
TC-054: dotCMS starts as the limited user; migration stays ON (Phase 1); no halt lines.
TC-055: published content lands in the OS index via the limited role; no 403.
TC-056: mismatched cluster id → 403 on own indexes; matching prefix fixes it.
TC-057: search/read through the limited user succeeds.
TC-058: removing cluster:monitor/main reproduces the unreachable→halt failure; restoring it recovers.
Parent Epic
#35476
Goal
Prove dotCMS can run the OpenSearch migration against an OS 3.x cluster reached through a
restricted, non-admin user (the managed-cloud setup validated in spike #35922) — not just an
open, security-disabled cluster. Covers provisioning the user, pointing dotCMS at it, and confirming
index operations work through the limited role.
Glossary (quick)
http://localhost:9200, plain HTTP, no auth.https://localhost:9201, TLS + basic auth..ostag: new-engine index names end in.os(e.g.cluster_dotcms….working_….os).Setup — limited-user stack + the provisioning script
Stack:
docker/docker-compose-examples/os-migration/docker-compose.limited-user.yml(OS 3.x withthe security plugin ON, provisioned with the non-admin
dotcms-es-user). Thedocker/docker-compose-examples/single-node-os-migration/variant runs OS 1.x + OS 3.x, provisionedthe same way.
Users on OS 3.x (port 9201, HTTPS):
adminDev!Search3-Kx9mP-2026dotcms-es-userDev!dotcms-EsUser-2026dotcms-role(limited)Provisioning script
opensearch.py— runs once via theopensearch-provisionservice; createsper customer an internal user
<customer>-es-user, action groups, a role<customer>-role, and therole mapping. Run it manually:
The
dotcms-rolegrants:cluster_<customer>*):indices_all,indices_monitorcluster:monitor/health,cluster:monitor/state,cluster:monitor/nodes/stats,indices:data/write/bulk,indices:data/read/scroll,indices:data/read/scroll/clear, andcluster:monitor/main(GET/version probe — the spike Spike: Validate dotCMS OpenSearch client requests against a non-admin OS 3.x user #35922 fix; without it dotCMS misreadsOS as unreachable and silently falls back to ES)
indices:monitor/stats,indices:monitor/settings/get,indices:admin/aliases/getcluster_<customer>*.dotCMS names indexes
cluster_<DOT_DOTCMS_CLUSTER_ID>.*, soDOT_DOTCMS_CLUSTER_IDmust start withthe customer name (e.g.
dotcms-os-migration) or the limited user gets 403 on its own indexes.dotCMS config:
Test Cases
TC-053 — Provision the limited user and verify the role/permissions (Risk: Medium)
opensearch-provisionfinish.curl -sk https://localhost:9201/_plugins/_security/api/internalusers?pretty -u admin:'Dev!Search3-Kx9mP-2026'→dotcms-es-userpresent.curl -sk https://localhost:9201/_plugins/_security/api/roles/dotcms-role?pretty -u admin:'Dev!Search3-Kx9mP-2026'.curl -sk https://localhost:9201/ -u dotcms-es-user:'Dev!dotcms-EsUser-2026'→ HTTP 200.dotcms-es-user+dotcms-roleexist with the listed permissions (incl.cluster:monitor/main);GET /as the limited user returns 200.TC-054 — dotCMS starts against OS as the limited user; migration NOT disabled (Risk: High)
dotcms).OS version check passed: 3.x; both ES and.osindexes listed → Phase 1.TC-055 — Dual-write works through the limited role (Phase 1) (Risk: High)
.os):curl -sk "https://localhost:9201/<cluster_dotcms….working_….os>/_doc/<id>" -u dotcms-es-user:'Dev!dotcms-EsUser-2026'.TC-056 — Cluster-id mismatch → limited user gets 403 on its own indexes (gotcha) (Risk: Medium)
DOT_DOTCMS_CLUSTER_ID=acme-test. Start in Phase 1.cluster_acme-test.*fail with 403 (outsidecluster_dotcms*); dotCMS logs the OS write failure (fire-and-forget in Phase 1 — ES unaffected). Fix: cluster id must start withdotcms(or re-provision with matching--customer) → 403 gone.TC-057 — Read/search works through the limited user (Risk: Medium)
curl -sk "https://localhost:9201/<…os>/_search?q=*:*" -u dotcms-es-user:'Dev!dotcms-EsUser-2026'.TC-058 — (Regression) Without
cluster:monitor/main, dotCMS misreads OS as unreachable (Risk: Medium)cluster:monitor/mainfromdotcms-role(security API, or re-provision without it).GET /as the limited user now returns 403.cluster:monitor/main(re-run the provisioner) → TC-054 passes again.Acceptance Criteria
dotcms-es-user/dotcms-roleprovisioned with expected perms (incl.cluster:monitor/main);GET /as limited user = 200.cluster:monitor/mainreproduces the unreachable→halt failure; restoring it recovers.Notes
cluster:monitor/maingrant is the key fix that keeps the OS version probe (client.info()) working for a non-admin user.