Skip to content

Conversation

cwperks
Copy link
Member

@cwperks cwperks commented Sep 2, 2025

Description

This PR builds on the change in #5596 to use DLS to filter the results behind-the-scenes so that a resource plugin only needs to search on the resource index and only get results visible to the authenticated user in the result-set.

Take an example of a resource doc with all_shared_principals:

{
  "name": "sharedDashboard",
  "description": "A dashboard resource that is shared with multiple principals",
  "type": "dashboard",
  "created_at": "2025-09-02T14:30:00Z",
  "attributes": {
    "category": "analytics",
    "sensitivity": "internal"
  },
  "all_shared_principals": [
    "user:resource_sharing_test_user_alice",
    "user:resource_sharing_test_user_bob",
    "role:analytics_team",
    "role:all_access",
    "role:auditor"
  ]
}

With the changes in this PR, if a plugin goes to search on this index (for instance as part of an API handler to list the resources or search resources), then security will apply a filter to limit the result set to resource docs that are shared with the authenticated user. That is, docs that are either owned by the authenticated user, are public (convention user:*) or explicitly shared with the user directly with username or one of the user's roles.

An example of the DLS filter applied would be:

{"terms":{"all_shared_principals.keyword":["user:resource_sharing_test_user_alice","user:*","role:all_access","role:analytics_team","backend_role:Developers"]}}
  • Category (Enhancement, New feature, Bug fix, Test fix, Refactoring, Maintenance, Documentation)

Enhancement

Issues Resolved

Related to: #4500

Check List

  • New functionality includes testing
  • New functionality has been documented
  • New Roles/Permissions have a corresponding security dashboards plugin PR
  • API changes companion pull request created
  • Commits are signed per the DCO using --signoff

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.

cwperks added 17 commits August 26, 2025 15:54
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
…le for searching

Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
@cwperks cwperks changed the title Use DLS to automatically filter sharable resources for authenticated user base on all_shared_principals Use DLS to automatically filter sharable resources for authenticated user based on all_shared_principals Sep 2, 2025
@cwperks cwperks changed the title Use DLS to automatically filter sharable resources for authenticated user based on all_shared_principals [Resource Sharing] Use DLS to automatically filter sharable resources for authenticated user based on all_shared_principals Sep 2, 2025
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Copy link

codecov bot commented Sep 2, 2025

Codecov Report

❌ Patch coverage is 55.22388% with 30 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.89%. Comparing base (c51ce46) to head (8c58c37).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
...rch/security/privileges/dlsfls/IndexToRuleMap.java 5.00% 19 Missing ⚠️
...search/security/configuration/DlsFlsValveImpl.java 65.00% 5 Missing and 2 partials ⚠️
...security/privileges/dlsfls/DocumentPrivileges.java 0.00% 3 Missing ⚠️
.../opensearch/security/OpenSearchSecurityPlugin.java 0.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #5600      +/-   ##
==========================================
- Coverage   72.95%   72.89%   -0.07%     
==========================================
  Files         408      408              
  Lines       25314    25260      -54     
  Branches     3855     3861       +6     
==========================================
- Hits        18469    18414      -55     
- Misses       4970     4977       +7     
+ Partials     1875     1869       -6     
Files with missing lines Coverage Δ
.../actions/transport/GetResourceTransportAction.java 90.47% <100.00%> (+1.79%) ⬆️
...tions/transport/SearchResourceTransportAction.java 100.00% <100.00%> (+36.36%) ⬆️
...ecurity/resources/ResourceAccessControlClient.java 77.77% <ø> (-4.05%) ⬇️
...arch/security/resources/ResourceAccessHandler.java 82.72% <ø> (+0.11%) ⬆️
...ecurity/resources/ResourceSharingIndexHandler.java 81.36% <ø> (+0.23%) ⬆️
...g/opensearch/security/support/WildcardMatcher.java 88.77% <100.00%> (+0.54%) ⬆️
.../opensearch/security/OpenSearchSecurityPlugin.java 85.00% <0.00%> (-0.11%) ⬇️
...security/privileges/dlsfls/DocumentPrivileges.java 90.74% <0.00%> (-5.34%) ⬇️
...search/security/configuration/DlsFlsValveImpl.java 64.66% <65.00%> (-0.67%) ⬇️
...rch/security/privileges/dlsfls/IndexToRuleMap.java 41.17% <5.00%> (-51.69%) ⬇️

... and 4 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.

* @param resourceIndex The index containing the resources.
* @param listener The listener to be notified with the set of accessible resources.
*/
void getAccessibleResourceIds(String resourceIndex, ActionListener<Set<String>> 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.

Makes sense. I'll add this back in until hierarchy is properly supported.

src.query(QueryBuilders.boolQuery().filter(accessQB));
return;
}
pluginClient.search(request, listener);
Copy link
Member

Choose a reason for hiding this comment

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

we need to update plugin-dev documentation to declare plugin as IdentityAwarePlugin.

* @param resourceIndex The index containing the resources.
* @param listener The listener to be notified with the set of accessible resources.
*/
void getAccessibleResourceIds(String resourceIndex, ActionListener<Set<String>> listener);
Copy link
Member

Choose a reason for hiding this comment

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

ML uses modelGroupIds to filter search for models. We need to keep this here until we support hierarchy.

dlsFlsBaseContext
dlsFlsBaseContext,
adminDns,
resourcePluginInfo != null ? resourcePluginInfo.getResourceIndices() : null
Copy link
Member

Choose a reason for hiding this comment

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

resourcePluginInfo will never be null since we instantiate it at class-level.

user.getRoles().forEach(r -> principals.add("role:" + r));
}

// Backend roles (LDAP/SAML/etc) – adjust getter name to your version!
Copy link
Member

Choose a reason for hiding this comment

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

nit: comment :D


List<String> principals = new ArrayList<>();
principals.add("user:*"); // Convention for publicly visible
principals.add("user:" + user.getName());
Copy link
Member

Choose a reason for hiding this comment

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

add a comment here like : // owner ?


// Build a single `terms` query JSON
String dlsJson = "{\"terms\":{\"all_shared_principals.keyword\":["
+ principals.stream().map(p -> "\"" + p.replace("\"", "\\\"") + "\"").collect(Collectors.joining(","))
Copy link
Member

Choose a reason for hiding this comment

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

for my understanding why do we need .replace?

Copy link
Member Author

Choose a reason for hiding this comment

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

Its not needed. I'm going to refactor this to use XContentBuilder for json to avoid assembling json like this.

* @param resourceIndex The resource index to check for accessible resources.
* @param listener The listener to be notified with the set of accessible resource IDs.
*/
public void getOwnAndSharedResourceIdsForCurrentUser(@NonNull String resourceIndex, ActionListener<Set<String>> listener) {
Copy link
Member

Choose a reason for hiding this comment

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

same as above, we need to leave this as is to support ML.

@@ -205,42 +205,6 @@ public void testHasPermission_pluginUserDenied() {
verify(listener).onResponse(false);
}

@Test
public void testGetOwnAndSharedResources_asAdmin() {
Copy link
Member

Choose a reason for hiding this comment

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

can leave this as is to test getAccessilbeResourceIds

Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
Signed-off-by: Craig Perkins <cwperx@amazon.com>
@@ -98,6 +99,7 @@ public boolean shouldEvaluate(ActionRequest request) {
ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT
);
if (!isResourceSharingFeatureEnabled) return false;
if (request instanceof GetRequest) return false;
Copy link
Member Author

Choose a reason for hiding this comment

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

GetRequest needs to be skipped here as that is evaluated as a cluster permission.

}

@Test
public void multipleRoles_multipleLevels() {
Copy link
Member Author

Choose a reason for hiding this comment

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

Added an addition test here for sharing with role name.

Signed-off-by: Craig Perkins <cwperx@amazon.com>
@@ -99,6 +100,7 @@ public boolean shouldEvaluate(ActionRequest request) {
);
if (!isResourceSharingFeatureEnabled) return false;
if (!(request instanceof DocRequest docRequest)) return false;
if (request instanceof GetRequest) return false;
Copy link
Member Author

Choose a reason for hiding this comment

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

Adding this here to handle the case where a plugin is making a GET Request to a system index using it's PluginSubject. In that case, the plugin should have permission to perform the GET, but because it wasn't being ignored here it was going through the resource access evaluation rather than the system index access evaluator.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants