Repository navigation
Conversation
…nsearch-project#6339) Introduces opt-in cursor-based pagination on the six caller-visible collection GETs (internalusers, roles, rolesmapping, actiongroups, tenants, nodesdn), mirroring the OpenSearch _list APIs (see opensearch-project/OpenSearch#14641). Contract: - size: positive page size, capped at 1000, defaulting to 100 when pagination is requested. - next_token: opaque Base64-encoded cursor bound to {format version, CType, sort direction, last returned entity name}. Null on the terminal page. - sort: asc or desc, default asc, sorting by configuration entity name. Backward compatibility: requests without any of size, next_token, or sort keep the exact pre-existing response shape. When pagination is requested, the response is wrapped as: {"next_token": ..., "<ctype>": { entries }} Single-entity GETs reject pagination parameters with HTTP 400. Malformed, tampered, cross-endpoint, or cross-direction cursors are rejected with 400. Continuation is name-based (lexical), so additions or deletions before the cursor do not shift later pages. Implementation: - New PaginationHelper (src/.../dlic/rest/api/pagination) exposes apply(request, ctype, securityConfiguration) which produces the wrapped ToXContent or a validation error. - New RequestHandlersBuilder.onCollectionGetRequest(ctype, mapper) wires the helper into the render step so endpoints opt in with a one-line change. - AbstractApiAction.prepareRequest consumes size / next_token / sort centrally (same pattern used for wait_for_completion) so non-participating endpoints ignore the parameters rather than 400-ing on them. - InternalUsersApiAction and NodesDnApiAction switch onGetRequest to onCollectionGetRequest, preserving filterBy and show_all customizations respectively. RolesApiAction, ActionGroupsApiAction, RolesMappingApiAction, and TenantsApiAction add an explicit onCollectionGetRequest call. Tests: - PaginationHelperTest (unit, 20 cases): asc/desc traversal, terminal null token, invalid size / sort / token / cross-endpoint / cross-direction / format-version mismatch, lexical continuation across deletions, empty collection, single-entity GET rejection, response shape. - PaginationRestApiIntegrationTest (integration, 16 cases): backwards- compatibility, asc/desc traversal, invalid input handling, cross-endpoint cursor rejection, single-entity GET rejection, lexical continuation with seeded deletions, hidden-entity non-leakage, internalusers filterBy composition, response shape wrapping. NodesDN pagination wiring is exercised via PaginationHelperTest and compilation; integration coverage is deferred because the test framework does not seed the nodesdn document in the security index. Signed-off-by: Nishtha Mittal <nishthm@amazon.com>
PR Reviewer Guide 🔍(Review updated until commit a8a755d)Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Latest suggestions up to a8a755d
Previous suggestionsSuggestions up to commit 3e7a96e
|
PR Code Analyzer ❗AI-powered 'Code-Diff-Analyzer' found issues on commit a8a755d. ⛔ Hard block: Issues at High severity or above will block this PR from merging.
The table above displays the top 10 most important findings. Pull Requests Author(s): Please update your Pull Request according to the report above. Repository Maintainer(s): You can Thanks. |
|
Persistent review updated to latest commit a8a755d |
|
There is already an existing PR #6378 raised by me that addresses the same issue. Maybe give it a look when you get a chance. |
Description
Introduces opt-in cursor-based pagination on the six caller-visible Security configuration collection GETs —
internalusers,roles,rolesmapping,actiongroups,tenants, andnodesdn— using the samesize/next_token/sortcontract as the OpenSearch_list/*APIs introduced in opensearch-project/OpenSearch#14641 and #14718. This gives operators one consistent pagination surface across core and plugin configuration APIs, closing the Security-plugin half of that consistency goal.Backward compatible. Requests without any of
size,next_token, orsortretain the exact pre-existing response shape byte-for-byte. Pagination is only active when a caller explicitly opts inContract
sizenext_tokensortasc|descascPaginated response:
Terminal page has
"next_token": null. The wrapper key is the endpoint'sCType.toLCString()(matches the URL segment).Cursor design
{"v":1,"c":"<ctype>","s":"<sort>","n":"<lastName>"}.{format version, CType, sort direction, last returned entity name}. Any mismatch — malformed / non-JSON / non-object / missing field / wrong field type / cross-endpoint / cross-direction / stale version — returns HTTP 400._list/*APIs use./roles/{name}) reject pagination parameters with HTTP 400.Diff shape
PaginationHelper.java(~400 LOC) — the whole feature lives here (cursor codec, param validation, page assembly, response wrapper).RequestHandler.java: one new builder methodonCollectionGetRequest(CType, mapper)— same mapper signature as the existingonGetRequest, so endpoint pipelines (permission checks,filterBy/show_allcomposition) are preserved verbatim.AbstractApiAction.java: consumessize/next_token/sortcentrally inprepareRequest, matching the pattern established bywait_for_completionin Support asynchronous Security configuration APIs with wait_for_completion #6337 — so singleton endpoints ignore the params silently rather than 400-ing on them.InternalUsersApiAction,NodesDnApiAction: swap.onGetRequest(mapper)for.onCollectionGetRequest(getConfigType(), mapper)— theirfilterBy/show_allcomposition inside the mapper is untouched.RolesApiAction,ActionGroupsApiAction,RolesMappingApiAction,TenantsApiAction: add.onCollectionGetRequest(getConfigType(), this::processGetRequest).Known trade-offs (documented in the design doc, flagged here for reviewers)
Nodesdn has no live-cluster integration test. The
LocalClusterframework does not seed thenodesdndocument in the security index, so a live GET returns 403 "Security index need to be updated to support 'nodesdn'" — that failure fires before pagination ever runs. The nodesdn pagination wiring is covered by (a)PaginationHelperTestwhich exercises the shared code path nodesdn now uses, (b) the one-call swap inNodesDnApiActionverifiable by code review, and (c) the existingNodesDnApiTestunit test. If reviewers prefer a live integration test, closing the gap is ~30–50 lines of test setup: obtain an admin-cert client vialocalCluster.getAdminCertRestClient()and seed the nodesdn document in@BeforeClass. Happy to add in a follow-up.Per-request rendering cost is O(N), not O(size).
PaginatedConfigurationResponse.toXContentserializes the full configuration once and then filters to the page. Chosen for byte-identical per-entity shape parity with the non-paginated path (no divergent serializer codepath to maintain). AtN = 10,000withsize = 100this is ~100× more work than strictly necessary. Not measurable at typical scales; if profiling on a large real deployment flags it, the fix is to serialize per-entity for names in the page (O(N)→O(size)), with a shape-diff regression test to keep both paths equivalent.Follow-ups intentionally out of scope
/_search-style pattern used by e.g. alerting). Larger architectural change — the plugin currently reads from an in-memorySecurityDynamicConfigurationsnapshot, not directly from.opendistro_security, and a search endpoint would need to reconcile with the DLS/FLS layer that already sits on top of that index. Trackable separately.loadConfiguration(omitSensitiveData=true)already exposes it; tenant behavior is unchanged.Rollout notes
vfield lets us bump the on-wire structure later and return a clean 400 on stale cursors rather than silently misbehaving.Base64.getUrlEncoder(), existingDefaultObjectMapper, existingSecurityDynamicConfiguration.audit,allowlist,securityconfig) accept the three new query params silently (consumed centrally) but never act on them.Issues Resolved
Closes #6339
Is this a backport? If so, please add backport PR # and/or commits #, and remove
backport-failedlabel from the original PR.N/a
Do these changes introduce new permission(s) to be displayed in the static dropdown on the front-end? If so, please open a draft PR in the security dashboards plugin and link the draft PR here
N/A
Testing
Unit —
PaginationHelperTest, 25 cases, all pass. Covers:isRequested/consumeParameterssemantics.sort, non-integer / out-of-rangesize, malformed Base64, non-JSON, valid-JSON-but-not-object, missing required field (each ofv/c/s/n), wrong field types, cross-endpoint cursor, cross-direction cursor, version-mismatched cursor.+,/,=).nulltoken, empty collection, single-entity GET rejection, response-shape wrapper containment.Integration —
PaginationRestApiIntegrationTest, 16 cases, all pass (real cluster viaLocalCluster,SECURITY_RESTAPI_ADMIN_ENABLED=true). Covers:next_token; single-entity GET unchanged./roles.next_token: null.size/sort/next_token→ HTTP 400 (five separate inputs)./rolesreplayed on/actiongroups→ HTTP 400.sort=ascreplayed withsort=desc→ HTTP 400./roles/{name}→ HTTP 400.roles,rolesmapping,actiongroups,internalusers,tenants(nodesdn deferred — see below).internalusers+filterBy=servicecomposes with pagination.next_token=treated as absent.Regression coverage. Full
org.opensearch.security.dlic.rest.api.*unit suite: 166 tests, 0 failures attributable to this change (one pre-existing flake onRollbackVersionApiTest.testRollbackToPreviousVersion_successthat reproduces on the base commit before this change is applied).Check List
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.