Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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))
- Make configuration setting for user custom attribute serialization dynamic ([#5657](https://github.com/opensearch-project/security/pull/5657))

### 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 @@ -2460,7 +2475,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