Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- [SecurityPlugin Health Check] Add AuthZ initialization completion check in health check API [(#5626)](https://github.com/opensearch-project/security/pull/5626)
- [Resource Sharing] Adds API to provide dashboards support for resource access management ([#5597](https://github.com/opensearch-project/security/pull/5597))
- Direct JWKS (JSON Web Key Set) support in the JWT authentication backend ([#5578](https://github.com/opensearch-project/security/pull/5578))
- Adds a list setting to explicitly specify resources to be protected ([#5671](https://github.com/opensearch-project/security/pull/5671))


### Bug Fixes
Expand Down
15 changes: 14 additions & 1 deletion RESOURCE_SHARING_AND_ACCESS_CONTROL.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,7 +506,9 @@ Since no entities are listed, the resource is accessible **only by its creator a

## Part 2: Cluster-admin and User guide

## **1. Feature Flag**
## **1. Setup**

### **Feature Flag**
This feature is controlled by the following flag:

- **Feature flag:** `plugins.security.experimental.resource_sharing.enabled`
Expand All @@ -515,6 +517,17 @@ This feature is controlled by the following flag:
```yaml
plugins.security.experimental.resource_sharing.enabled: true
```
### **List protected types**

The list of protected types are controlled through following opensearch setting

- **Setting:** `plugins.security.experimental.resource_sharing.protected_types`
- **Default value:** `[]`
- **How to specify a type?** Add entries of existing types in the list:
```yaml
plugins.security.experimental.resource_sharing.protected_types: [sample-resource]
```
NOTE: These types will be available on documentation website.

## **2. User Setup**

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.is;
import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME;
import static org.opensearch.sample.utils.Constants.RESOURCE_TYPE;
import static org.opensearch.sample.utils.Constants.SAMPLE_RESOURCE_PLUGIN_PREFIX;
import static org.opensearch.security.resources.ResourceSharingIndexHandler.getSharingIndex;
import static org.opensearch.security.support.ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED;
import static org.opensearch.security.support.ConfigConstants.OPENSEARCH_RESOURCE_SHARING_PROTECTED_TYPES;
import static org.opensearch.security.support.ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY;
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL;
import static org.opensearch.test.framework.TestSecurityConfig.User.USER_ADMIN;
Expand Down Expand Up @@ -90,6 +92,10 @@ public final class TestUtils {
public static final String SECURITY_LIST_ENDPOINT = "_plugins/_security/api/resource/list";

public static LocalCluster newCluster(boolean featureEnabled, boolean systemIndexEnabled) {
return newCluster(featureEnabled, systemIndexEnabled, List.of(RESOURCE_TYPE));
}

public static LocalCluster newCluster(boolean featureEnabled, boolean systemIndexEnabled, List<String> protectedResourceTypes) {
return new LocalCluster.Builder().clusterManager(ClusterManager.THREE_CLUSTER_MANAGERS_COORDINATOR)
.plugin(
new PluginInfo(
Expand All @@ -108,7 +114,14 @@ public static LocalCluster newCluster(boolean featureEnabled, boolean systemInde
.authc(AUTHC_HTTPBASIC_INTERNAL)
.users(USER_ADMIN, FULL_ACCESS_USER, LIMITED_ACCESS_USER, NO_ACCESS_USER)
.nodeSettings(
Map.of(OPENSEARCH_RESOURCE_SHARING_ENABLED, featureEnabled, SECURITY_SYSTEM_INDICES_ENABLED_KEY, systemIndexEnabled)
Map.of(
OPENSEARCH_RESOURCE_SHARING_ENABLED,
featureEnabled,
SECURITY_SYSTEM_INDICES_ENABLED_KEY,
systemIndexEnabled,
OPENSEARCH_RESOURCE_SHARING_PROTECTED_TYPES,
protectedResourceTypes
)
)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,15 +284,11 @@ public void testRawAccess_allAccessUser() {
api.assertDirectShare(userResId, FULL_ACCESS_USER, USER_ADMIN, SAMPLE_READ_ONLY_RESOURCE_AG, HttpStatus.SC_OK);
api.assertDirectGet(userResId, USER_ADMIN, HttpStatus.SC_OK, "sample");

// can only access own resource since
api.assertDirectGetSearch(FULL_ACCESS_USER, HttpStatus.SC_OK, 2, "sample");
api.assertDirectPostSearch(searchAllPayload(), FULL_ACCESS_USER, HttpStatus.SC_OK, 2, "sample");
api.assertDirectPostSearch(searchByNamePayload("sample"), FULL_ACCESS_USER, HttpStatus.SC_OK, 1, "sample");
api.assertDirectPostSearch(searchByNamePayload("sampleUser"), FULL_ACCESS_USER, HttpStatus.SC_OK, 1, "sampleUser");

// cannot update or delete resource
api.assertDirectUpdate(adminResId, FULL_ACCESS_USER, "sampleUpdateAdmin", HttpStatus.SC_FORBIDDEN);
api.assertDirectDelete(adminResId, FULL_ACCESS_USER, HttpStatus.SC_FORBIDDEN);
// can update and delete own resource
api.assertDirectUpdate(userResId, FULL_ACCESS_USER, "sampleUpdateUser", HttpStatus.SC_OK);
api.assertDirectDelete(userResId, FULL_ACCESS_USER, HttpStatus.SC_OK);
Expand All @@ -302,6 +298,10 @@ public void testRawAccess_allAccessUser() {
api.assertDirectShare(adminResId, FULL_ACCESS_USER, NO_ACCESS_USER, SAMPLE_FULL_ACCESS_RESOURCE_AG, HttpStatus.SC_OK);
api.assertDirectRevoke(adminResId, FULL_ACCESS_USER, NO_ACCESS_USER, SAMPLE_FULL_ACCESS_RESOURCE_AG, HttpStatus.SC_OK);
api.assertDirectDeleteResourceSharingRecord(adminResId, FULL_ACCESS_USER, HttpStatus.SC_OK);

// can update or delete admin resource, since system index protection is disabled and user has direct index access.
api.assertDirectUpdate(adminResId, FULL_ACCESS_USER, "sampleUpdateAdmin", HttpStatus.SC_OK);
api.assertDirectDelete(adminResId, FULL_ACCESS_USER, HttpStatus.SC_OK);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.sample.resource.feature.enabled;

import java.util.List;

import com.carrotsearch.randomizedtesting.RandomizedRunner;
import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
import org.apache.http.HttpStatus;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.opensearch.sample.resource.TestUtils;
import org.opensearch.test.framework.cluster.LocalCluster;
import org.opensearch.test.framework.cluster.TestRestClient;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.opensearch.sample.resource.TestUtils.FULL_ACCESS_USER;
import static org.opensearch.sample.resource.TestUtils.LIMITED_ACCESS_USER;
import static org.opensearch.sample.resource.TestUtils.NO_ACCESS_USER;
import static org.opensearch.sample.resource.TestUtils.RESOURCE_SHARING_INDEX;
import static org.opensearch.sample.resource.TestUtils.newCluster;
import static org.opensearch.test.framework.TestSecurityConfig.User.USER_ADMIN;

/**
* Test resource access when sample-resource is not marked as protected resource, even-though resource sharing protection is enabled.
*/
@RunWith(RandomizedRunner.class)
@ThreadLeakScope(ThreadLeakScope.Scope.NONE)
public class ExcludedResourceTypeTests {

// do not include sample resource as protected resource, should behave as if feature was disable for that resource
@ClassRule
public static LocalCluster cluster = newCluster(true, true, List.of());

private final TestUtils.ApiHelper api = new TestUtils.ApiHelper(cluster);
private String resourceId;

@Before
public void setup() {
resourceId = api.createSampleResourceAs(USER_ADMIN);
}

@After
public void cleanup() {
api.wipeOutResourceEntries();
}

@Test
public void testSampleResourceSharingIndexDoesNotExist() {
try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) {
TestRestClient.HttpResponse response = client.get("_cat/indices?expand_wildcards=all");
response.assertStatusCode(HttpStatus.SC_OK);
assertThat(response.getBody(), not(containsString(RESOURCE_SHARING_INDEX)));
}
}

@Test
public void fullAccessUser_canCRUD() {
api.assertApiGet(resourceId, FULL_ACCESS_USER, HttpStatus.SC_OK, "sample");
api.assertApiUpdate(resourceId, FULL_ACCESS_USER, "sampleUpdateAdmin", HttpStatus.SC_OK);
api.assertApiDelete(resourceId, FULL_ACCESS_USER, HttpStatus.SC_OK);
}

@Test
public void limitedAccessUser_canCRUD() {
api.assertApiGet(resourceId, LIMITED_ACCESS_USER, HttpStatus.SC_OK, "sample");
api.assertApiUpdate(resourceId, LIMITED_ACCESS_USER, "sampleUpdateAdmin", HttpStatus.SC_FORBIDDEN);
api.assertApiDelete(resourceId, LIMITED_ACCESS_USER, HttpStatus.SC_FORBIDDEN);
}

@Test
public void noAccessUser_canCRUD() {
api.assertApiGet(resourceId, NO_ACCESS_USER, HttpStatus.SC_FORBIDDEN, "");
api.assertApiUpdate(resourceId, NO_ACCESS_USER, "sampleUpdateAdmin", HttpStatus.SC_FORBIDDEN);
api.assertApiDelete(resourceId, NO_ACCESS_USER, HttpStatus.SC_FORBIDDEN);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,10 @@
import static org.opensearch.sample.resource.TestUtils.migrationPayload_valid;
import static org.opensearch.sample.resource.TestUtils.migrationPayload_valid_withSpecifiedAccessLevel;
import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME;
import static org.opensearch.sample.utils.Constants.RESOURCE_TYPE;
import static org.opensearch.security.resources.ResourceSharingIndexHandler.getSharingIndex;
import static org.opensearch.security.support.ConfigConstants.OPENSEARCH_RESOURCE_SHARING_ENABLED;
import static org.opensearch.security.support.ConfigConstants.OPENSEARCH_RESOURCE_SHARING_PROTECTED_TYPES;
import static org.opensearch.security.support.ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY;
import static org.opensearch.test.framework.TestSecurityConfig.AuthcDomain.AUTHC_HTTPBASIC_INTERNAL;

Expand Down Expand Up @@ -79,7 +81,16 @@ public class MigrateApiTests {
.anonymousAuth(false)
.authc(AUTHC_HTTPBASIC_INTERNAL)
.users(MIGRATION_USER)
.nodeSettings(Map.of(SECURITY_SYSTEM_INDICES_ENABLED_KEY, true, OPENSEARCH_RESOURCE_SHARING_ENABLED, true))
.nodeSettings(
Map.of(
SECURITY_SYSTEM_INDICES_ENABLED_KEY,
true,
OPENSEARCH_RESOURCE_SHARING_ENABLED,
true,
OPENSEARCH_RESOURCE_SHARING_PROTECTED_TYPES,
List.of(RESOURCE_TYPE)
)
)
.build();

@After
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.opensearch.sample.SampleResource;

import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME;
import static org.opensearch.sample.utils.Constants.RESOURCE_TYPE;

/**
* Request object for CreateSampleResource transport action
Expand Down Expand Up @@ -59,6 +60,11 @@ public boolean shouldStoreUser() {
return this.shouldStoreUser;
}

@Override
public String type() {
return RESOURCE_TYPE;
}

@Override
public String index() {
return RESOURCE_INDEX_NAME;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.opensearch.sample.SampleResource;

import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME;
import static org.opensearch.sample.utils.Constants.RESOURCE_TYPE;

/**
* Request object for UpdateResource transport action
Expand Down Expand Up @@ -59,6 +60,11 @@ public String getResourceId() {
return this.resourceId;
}

@Override
public String type() {
return RESOURCE_TYPE;
}

@Override
public String index() {
return RESOURCE_INDEX_NAME;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.opensearch.core.common.io.stream.StreamOutput;

import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME;
import static org.opensearch.sample.utils.Constants.RESOURCE_TYPE;

/**
* Request object for DeleteSampleResource transport action
Expand Down Expand Up @@ -50,6 +51,11 @@ public String getResourceId() {
return this.resourceId;
}

@Override
public String type() {
return RESOURCE_TYPE;
}

@Override
public String index() {
return RESOURCE_INDEX_NAME;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.opensearch.core.common.io.stream.StreamOutput;

import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME;
import static org.opensearch.sample.utils.Constants.RESOURCE_TYPE;

/**
* Request object for GetSampleResource transport action
Expand Down Expand Up @@ -50,6 +51,11 @@ public String getResourceId() {
return this.resourceId;
}

@Override
public String type() {
return RESOURCE_TYPE;
}

@Override
public String index() {
return RESOURCE_INDEX_NAME;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.opensearch.security.spi.resources.sharing.ShareWith;

import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME;
import static org.opensearch.sample.utils.Constants.RESOURCE_TYPE;

/**
* Request object for revoking access to a sample resource
Expand Down Expand Up @@ -56,6 +57,11 @@ public ShareWith getEntitiesToRevoke() {
return revokeAccess;
}

@Override
public String type() {
return RESOURCE_TYPE;
}

@Override
public String index() {
return RESOURCE_INDEX_NAME;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.opensearch.security.spi.resources.sharing.ShareWith;

import static org.opensearch.sample.utils.Constants.RESOURCE_INDEX_NAME;
import static org.opensearch.sample.utils.Constants.RESOURCE_TYPE;

/**
* Request object for sharing sample resource transport action
Expand Down Expand Up @@ -57,6 +58,11 @@ public ShareWith getShareWith() {
return shareWithRecipients;
}

@Override
public String type() {
return RESOURCE_TYPE;
}

@Override
public String index() {
return RESOURCE_INDEX_NAME;
Expand Down
5 changes: 5 additions & 0 deletions spi/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ public class ShareResourceRequest extends ActionRequest implements DocRequest {
return shareWithRecipients;
}

@Override
public String type() {
return RESOURCE_TYPE;
}

@Override
public String index() {
return RESOURCE_INDEX_NAME;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1225,7 +1225,10 @@ public Collection<Object> createComponents(
// CS-SUPPRESS-SINGLE: RegexpSingleline get Resource Sharing Extensions
// Assign resource sharing client to each extension
// Using the non-gated client (i.e. no additional permissions required)
ResourceSharingClient resourceAccessControlClient = new ResourceAccessControlClient(resourceAccessHandler);
ResourceSharingClient resourceAccessControlClient = new ResourceAccessControlClient(
resourceAccessHandler,
resourcePluginInfo.getResourceIndices()
);
resourcePluginInfo.getResourceSharingExtensions().forEach(extension -> {
extension.assignResourceSharingClient(resourceAccessControlClient);
});
Expand Down Expand Up @@ -2236,6 +2239,18 @@ public List<Setting<?>> getSettings() {
)
);

// resource marked here will be protected, other resources will not be protected with resource sharing model
// Defaults to no resources as protected
settings.add(
Setting.listSetting(
ConfigConstants.OPENSEARCH_RESOURCE_SHARING_PROTECTED_TYPES,
ConfigConstants.OPENSEARCH_RESOURCE_SHARING_PROTECTED_TYPES_DEFAULT,
Function.identity(),
Property.NodeScope,
Property.Filtered
)
);

settings.add(UserFactory.Caching.MAX_SIZE);
settings.add(UserFactory.Caching.EXPIRE_AFTER_ACCESS);

Expand Down Expand Up @@ -2456,7 +2471,10 @@ public void loadExtensions(ExtensionLoader loader) {

// discover & register extensions and their types
Set<ResourceSharingExtension> exts = new HashSet<>(loader.loadExtensions(ResourceSharingExtension.class));
resourcePluginInfo.setResourceSharingExtensions(exts);
resourcePluginInfo.setResourceSharingExtensions(
exts,
settings.getAsList(ConfigConstants.OPENSEARCH_RESOURCE_SHARING_PROTECTED_TYPES)
);

// load action-groups in memory
ResourceActionGroupsHelper.loadActionGroupsConfig(resourcePluginInfo);
Expand Down
Loading
Loading