Skip to content

Conversation

DarshitChanpura
Copy link
Member

Description

[Describe what this change achieves]

Related Issues

Resolves #[Issue number to be closed when this PR is merged]

Check List

  • New functionality includes testing.
  • New functionality has been documented.
  • API changes companion pull request created.
  • Commits are signed per the DCO using --signoff.
  • Public documentation issue/PR created.

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.

@opensearch-trigger-bot opensearch-trigger-bot bot added infra Changes to infrastructure, testing, CI/CD, pipelines, etc. backport 2.x labels Aug 12, 2025
Copy link

codecov bot commented Aug 13, 2025

Codecov Report

❌ Patch coverage is 41.48936% with 55 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.42%. Comparing base (17e56f0) to head (2a4fadf).

Files with missing lines Patch % Lines
...ava/org/opensearch/timeseries/util/ParseUtils.java 14.81% 20 Missing and 3 partials ⚠️
...transport/DeleteAnomalyResultsTransportAction.java 5.55% 17 Missing ⚠️
...arch/ad/transport/IndexAnomalyDetectorRequest.java 0.00% 2 Missing ⚠️
...rch/forecast/transport/IndexForecasterRequest.java 0.00% 2 Missing ⚠️
...ch/timeseries/transport/handler/SearchHandler.java 50.00% 1 Missing and 1 partial ⚠️
...transport/IndexAnomalyDetectorTransportAction.java 50.00% 1 Missing ⚠️
...ansport/PreviewAnomalyDetectorTransportAction.java 50.00% 1 Missing ⚠️
...cast/transport/ForecastRunOnceTransportAction.java 50.00% 1 Missing ⚠️
...cast/transport/IndexForecasterTransportAction.java 88.88% 1 Missing ⚠️
.../transport/SearchForecastTasksTransportAction.java 0.00% 1 Missing ⚠️
... and 4 more
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main    #1546      +/-   ##
============================================
- Coverage     81.59%   81.42%   -0.18%     
- Complexity     6039     6041       +2     
============================================
  Files           536      536              
  Lines         24427    24474      +47     
  Branches       2494     2500       +6     
============================================
- Hits          19932    19928       -4     
- Misses         3260     3309      +49     
- Partials       1235     1237       +2     
Flag Coverage Δ
plugin 81.42% <41.48%> (-0.18%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
.../java/org/opensearch/ad/model/AnomalyDetector.java 89.93% <100.00%> (+0.06%) ⬆️
...rch/ad/transport/SearchADTasksTransportAction.java 100.00% <100.00%> (ø)
...ransport/SearchAnomalyDetectorTransportAction.java 100.00% <100.00%> (ø)
.../transport/SearchAnomalyResultTransportAction.java 86.66% <100.00%> (ø)
...ansport/SearchTopAnomalyResultTransportAction.java 92.16% <100.00%> (ø)
...java/org/opensearch/forecast/model/Forecaster.java 80.22% <100.00%> (-0.92%) ⬇️
...nsport/SearchTopForecastResultTransportAction.java 66.12% <100.00%> (ø)
...n/java/org/opensearch/timeseries/model/Config.java 86.92% <100.00%> (+0.04%) ⬆️
...ies/transport/BaseDeleteConfigTransportAction.java 83.63% <100.00%> (+0.75%) ⬆️
...h/timeseries/transport/BaseJobTransportAction.java 90.00% <100.00%> (+2.50%) ⬆️
... and 14 more

... and 6 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Signed-off-by: Darshit Chanpura <[email protected]>
@@ -465,6 +465,11 @@ public static AnomalyDetector parse(
parser.nextToken();

switch (fieldName) {
case ID_FIELD:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needed for access control on Validate method.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there be a privilege escalation when one user writes someone else's id in the request body?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No they cannot, all resource related requests are first checked for permission here, before proceeding to execute it:
https://github.com/opensearch-project/security/blob/main/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java#L67

@@ -145,4 +147,13 @@ public ActionRequestValidationException validate() {
return null;
}

@Override
public String index() {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this handles the Update scenario.

),
new Object[] {},
(fallbackArgs) -> resolveUserAndExecute(
() -> indexDetector(user, detectorId, method, listener, detector -> adExecute(request, user, detector, context, listener)),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

most of these changes around verifyResourceAccessAndProcessRequest are code cleanup.

* @param actionListener action listerner
*/
public void search(SearchRequest request, ActionListener<SearchResponse> actionListener) {
public void search(SearchRequest request, Pair<String, String> pair, ActionListener<SearchResponse> actionListener) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please review this. This change currently modifies all searchHandler.search calls to pass the index name and id field of the config. However, the search calls that are made from tasks and results indices are not covered by resource-authz. Only AD and forecast config index are, since those two are registered as protected indices.

logger.debug("Filtering result by accessible resources");
ResourceSharingClient resourceSharingClient = ResourceSharingClientAccessor.getInstance().getResourceSharingClient();
SearchSourceBuilder searchSourceBuilder = searchRequest.source();
resourceSharingClient.getAccessibleResourceIds(pair.getLeft(), ActionListener.wrap(configIds -> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes as call to get accessible Id and then adds a filter to current search query to only search from those ids.

// case 5: given a forecaster Id, read access user can
if (isResourceSharingFeatureEnabled()) {
// not look up forecaster configuration for full-client's forecaster
exception = expectThrows(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

user won't have access by default.

@@ -1471,6 +1627,309 @@ public void testFilterBy() throws IOException {
}
}

public void testResourceSharing() throws IOException {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adds test specifically for resource sharing feature

}, notNullValue());
}

private void waitForRevokeNonVisibility(String detId, RestClient client) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to test that resource is no longer visible.

String oceanUser = "ocean";
RestClient oceanClient;

private void waitForSharingVisibility(String detId, RestClient client) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to test that resource is visible.

.assertTrue(
exception.getMessage().contains("Filter by backend roles is enabled and User dog does not have backend roles configured")
);
if (isResourceSharingFeatureEnabled()) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

covers resource sharing feature enabled scenario.

Copy link
Collaborator

@kaituo kaituo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

read until src/main/java/org/opensearch/ad/transport/IndexAnomalyDetectorRequest.java

@@ -70,19 +79,46 @@ public void delete(DeleteByQueryRequest request, ActionListener<BulkByScrollResp
}

private void validateRole(DeleteByQueryRequest request, User user, ActionListener<BulkByScrollResponse> listener) {
if (user == null || !filterEnabled) {
if (user == null || !(filterEnabled || shouldUseResourceAuthz)) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you update comment as you add a new condition?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

ParseUtils.addUserBackendRolesFilter(user, request.getSearchRequest().source());
// Security is enabled and resource sharing access control is enabled
if (shouldUseResourceAuthz) {
// verify if new authz should be added on result index, if soo replace the null
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

soo -> so

@@ -465,6 +465,11 @@ public static AnomalyDetector parse(
parser.nextToken();

switch (fieldName) {
case ID_FIELD:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there be a privilege escalation when one user writes someone else's id in the request body?

try {
ParseUtils.addUserBackendRolesFilter(user, request.getSearchRequest().source());
// Security is enabled and resource sharing access control is enabled
if (shouldUseResourceAuthz) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if both shouldUseResourceAuthz and filterEnabled are true? In your current code, would that increase a user's privilege as you only check shouldUseResourceAuthz? This can happen in an old cluster when backend role filtering is enabled.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't intend for mixed cluster usage. If both are enabled, resource-authz takes precedence. At that point, cluster-admin is expected to have migrated resources from old sharing model to the new one.

if (shouldUseResourceAuthz) {
// verify if new authz should be added on result index, if soo replace the null
addAccessibleConfigsFilterAndDelete(ADIndex.RESULT.getIndexName(), request.getSearchRequest(), listener);
return;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is using a terms filter (ideally via terms lookup) directly inside Delete-By-Query is better than a separate “fetch IDs → inject into DBQ” step? Terms filter keeps authorization and deletion in a single request, eliminating the read-then-delete” race where ACLs/ownership can change between calls. DBQ will return version conflict error during race condition (see https://discuss.elastic.co/t/concurrent-delete-by-query-and-indexing/68868/2
). Also, writing a single DBQ evaluates the latest ACLs at request time and enforces least-privilege by intersecting resource-based authz with any backend-role filter in the same bool query—so enabling both cannot widen access. It also removes a network round trip, reduces latency and error surface, and keeps the request small—no 1,000-ID terms array bloating the query body—while avoiding large in-memory ID lists in the transport layer. The control flow becomes linear (assemble filters → run one DBQ), easier to read, test, and maintain, and if no resources are accessible the filter simply matches none (fail-closed).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before I answer this is ADResult Index going to be a protected index? if so we should mark is as such in TimeSeriesResourceSharingExtension.

…cally, adds resource-action-groups yml for sharing and pluginClient for search

Signed-off-by: Darshit Chanpura <[email protected]>
Copy link
Member Author

@DarshitChanpura DarshitChanpura left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI is expected to fail until opensearch-project/security#5597 is merged.

SearchWrapper for resource-sharing requests is now being handled DLS style and will be available once this PR merges: opensearch-project/security#5600

@@ -465,6 +465,11 @@ public static AnomalyDetector parse(
parser.nextToken();

switch (fieldName) {
case ID_FIELD:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No they cannot, all resource related requests are first checked for permission here, before proceeding to execute it:
https://github.com/opensearch-project/security/blob/main/src/main/java/org/opensearch/security/privileges/ResourceAccessEvaluator.java#L67

@@ -70,19 +79,46 @@ public void delete(DeleteByQueryRequest request, ActionListener<BulkByScrollResp
}

private void validateRole(DeleteByQueryRequest request, User user, ActionListener<BulkByScrollResponse> listener) {
if (user == null || !filterEnabled) {
if (user == null || !(filterEnabled || shouldUseResourceAuthz)) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

try {
ParseUtils.addUserBackendRolesFilter(user, request.getSearchRequest().source());
// Security is enabled and resource sharing access control is enabled
if (shouldUseResourceAuthz) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't intend for mixed cluster usage. If both are enabled, resource-authz takes precedence. At that point, cluster-admin is expected to have migrated resources from old sharing model to the new one.

if (shouldUseResourceAuthz) {
// verify if new authz should be added on result index, if soo replace the null
addAccessibleConfigsFilterAndDelete(ADIndex.RESULT.getIndexName(), request.getSearchRequest(), listener);
return;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

before I answer this is ADResult Index going to be a protected index? if so we should mark is as such in TimeSeriesResourceSharingExtension.

Settings settings,
ClusterService clusterService,
Client client,
PluginClient pluginClient,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PluginClient acts as a wrapper for search and security plugin will apply DLS automatically through it for resource-sharing requests

@@ -0,0 +1,31 @@
# no internal actions are included here
resource_types:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please review this, as these are the action-groups that will be displayed on dashboard page for resource-access-management.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
infra Changes to infrastructure, testing, CI/CD, pipelines, etc.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants