Skip to content
Open
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased 3.x]
### Added
- Introduced new experimental versioned security configuration management feature ([#5357] (https://github.com/opensearch-project/security/pull/5357))

- Create a mechanism for plugins to explicitly declare actions they need to perform with their assigned PluginSubject ([#5341](https://github.com/opensearch-project/security/pull/5341))

### Changed

Expand Down
10 changes: 5 additions & 5 deletions check-permissions-order.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const yaml = require('yaml')
function checkPermissionsOrder(file, fix = false) {
const contents = fs.readFileSync(file, 'utf8')
const doc = yaml.parseDocument(contents, { keepCstNodes: true })
const roles = doc.contents.items
const roles = doc.contents?.items || []
let requiresChanges = false
roles.forEach(role => {
const itemsFromRole = role?.value?.items;
Expand Down Expand Up @@ -73,14 +73,14 @@ if (args.length === 0) {
}
const filePath = args[0]
const fix = args.includes('--fix')
const slient = args.includes('--slient')
const silent = args.includes('--silent')
if (checkPermissionsOrder(filePath, fix)) {
if (fix) {
if (!slient) { console.log(`${filePath} has been updated.`) }
if (!silent) { console.log(`${filePath} has been updated.`) }
} else {
if (!slient) { console.error(`Error: ${filePath} requires changes.`) }
if (!silent) { console.error(`Error: ${filePath} requires changes.`) }
process.exit(1)
}
} else {
if (!slient) { console.log(`${filePath} is up-to-date.`) }
if (!silent) { console.log(`${filePath} is up-to-date.`) }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright OpenSearch Contributors
* 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.secure;

import java.util.List;
import java.util.Map;

import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.opensearch.Version;
import org.opensearch.core.rest.RestStatus;
import org.opensearch.painless.PainlessModulePlugin;
import org.opensearch.plugins.PluginInfo;
import org.opensearch.sample.SampleResourcePlugin;
import org.opensearch.security.OpenSearchSecurityPlugin;
import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain;
import org.opensearch.test.framework.cluster.ClusterManager;
import org.opensearch.test.framework.cluster.LocalCluster;
import org.opensearch.test.framework.cluster.TestRestClient;
import org.opensearch.test.framework.cluster.TestRestClient.HttpResponse;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.opensearch.sample.utils.Constants.SAMPLE_RESOURCE_PLUGIN_PREFIX;
import static org.opensearch.security.support.ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY;
import static org.opensearch.test.framework.TestSecurityConfig.User.USER_ADMIN;

@RunWith(com.carrotsearch.randomizedtesting.RandomizedRunner.class)
@ThreadLeakScope(ThreadLeakScope.Scope.NONE)
public class SecurePluginTests {

public static final AuthcDomain AUTHC_DOMAIN = new AuthcDomain("basic", 0).httpAuthenticatorWithChallenge("basic").backend("internal");

@ClassRule
public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE)
.anonymousAuth(false)
.authc(AUTHC_DOMAIN)
.users(USER_ADMIN)
.plugin(PainlessModulePlugin.class)
.plugin(
new PluginInfo(
SampleResourcePlugin.class.getName(),
"classpath plugin",
"NA",
Version.CURRENT,
"1.8",
SampleResourcePlugin.class.getName(),
null,
List.of(OpenSearchSecurityPlugin.class.getName()),
false
)
)
.nodeSettings(Map.of(SECURITY_SYSTEM_INDICES_ENABLED_KEY, true))
.build();

@Test
public void testRunClusterHealthWithPluginSubject() {
try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
HttpResponse response = client.postJson(SAMPLE_RESOURCE_PLUGIN_PREFIX + "/run_action", """
{
"action": "cluster:monitor/health"
}
""");

assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus()));
assertThat(response.getBody(), containsString("number_of_nodes"));
}
}

@Test
public void testRunCreateIndexWithPluginSubject() {
try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) {
HttpResponse response = client.postJson(SAMPLE_RESOURCE_PLUGIN_PREFIX + "/run_action", """
{
"action": "indices:admin/create",
"index": "test-index"
}
""");

assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus()));
HttpResponse catIndicesResponse = client.get("_cat/indices");
assertThat(catIndicesResponse.getBody(), containsString("test-index"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@
import org.opensearch.core.xcontent.NamedXContentRegistry;
import org.opensearch.env.Environment;
import org.opensearch.env.NodeEnvironment;
import org.opensearch.identity.PluginSubject;
import org.opensearch.indices.SystemIndexDescriptor;
import org.opensearch.plugins.ActionPlugin;
import org.opensearch.plugins.IdentityAwarePlugin;
import org.opensearch.plugins.Plugin;
import org.opensearch.plugins.SystemIndexPlugin;
import org.opensearch.repositories.RepositoriesService;
Expand All @@ -54,6 +56,10 @@
import org.opensearch.sample.resource.actions.transport.RevokeResourceAccessTransportAction;
import org.opensearch.sample.resource.actions.transport.ShareResourceTransportAction;
import org.opensearch.sample.resource.actions.transport.UpdateResourceTransportAction;
import org.opensearch.sample.secure.actions.rest.create.SecurePluginAction;
import org.opensearch.sample.secure.actions.rest.create.SecurePluginRestAction;
import org.opensearch.sample.secure.actions.transport.SecurePluginTransportAction;
import org.opensearch.sample.utils.RunAsSubjectClient;
import org.opensearch.script.ScriptService;
import org.opensearch.threadpool.ThreadPool;
import org.opensearch.transport.client.Client;
Expand All @@ -68,10 +74,12 @@
* It uses ".sample_resource_sharing_plugin" index to manage its resources, and exposes few REST APIs that manage CRUD operations on sample resources.
*
*/
public class SampleResourcePlugin extends Plugin implements ActionPlugin, SystemIndexPlugin {
public class SampleResourcePlugin extends Plugin implements ActionPlugin, SystemIndexPlugin, IdentityAwarePlugin {
private static final Logger log = LogManager.getLogger(SampleResourcePlugin.class);
private boolean isResourceSharingEnabled = false;

private RunAsSubjectClient pluginClient;

public SampleResourcePlugin(final Settings settings) {
isResourceSharingEnabled = settings.getAsBoolean(OPENSEARCH_RESOURCE_SHARING_ENABLED, OPENSEARCH_RESOURCE_SHARING_ENABLED_DEFAULT);
}
Expand All @@ -90,7 +98,8 @@ public Collection<Object> createComponents(
IndexNameExpressionResolver indexNameExpressionResolver,
Supplier<RepositoriesService> repositoriesServiceSupplier
) {
return Collections.emptyList();
this.pluginClient = new RunAsSubjectClient(client);
return List.of(pluginClient);
}

@Override
Expand All @@ -107,6 +116,7 @@ public List<RestHandler> getRestHandlers(
handlers.add(new CreateResourceRestAction());
handlers.add(new GetResourceRestAction());
handlers.add(new DeleteResourceRestAction());
handlers.add(new SecurePluginRestAction());

if (isResourceSharingEnabled) {
handlers.add(new ShareResourceRestAction());
Expand All @@ -126,6 +136,7 @@ public List<RestHandler> getRestHandlers(
actions.add(new ActionHandler<>(ShareResourceAction.INSTANCE, ShareResourceTransportAction.class));
actions.add(new ActionHandler<>(RevokeResourceAccessAction.INSTANCE, RevokeResourceAccessTransportAction.class));
}
actions.add(new ActionHandler<>(SecurePluginAction.INSTANCE, SecurePluginTransportAction.class));
return actions;
}

Expand All @@ -134,4 +145,11 @@ public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings sett
final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(RESOURCE_INDEX_NAME, "Sample index with resources");
return Collections.singletonList(systemIndexDescriptor);
}

@Override
public void assignSubject(PluginSubject pluginSubject) {
if (this.pluginClient != null) {
this.pluginClient.setSubject(pluginSubject);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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.secure.actions.rest.create;

import org.opensearch.action.ActionType;

/**
* Action for testing running actions with PluginSubject
*/
public class SecurePluginAction extends ActionType<SecurePluginResponse> {
/**
* Secure plugin action instance
*/
public static final SecurePluginAction INSTANCE = new SecurePluginAction();
/**
* Secure plugin action name
*/
public static final String NAME = "cluster:admin/sample-resource-plugin/run-actions";

private SecurePluginAction() {
super(NAME, SecurePluginResponse::new);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* 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.secure.actions.rest.create;

import java.io.IOException;

import org.opensearch.action.ActionRequest;
import org.opensearch.action.ActionRequestValidationException;
import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.core.common.io.stream.StreamOutput;

/**
* Request object for SecurePluginAction transport action
*/
public class SecurePluginRequest extends ActionRequest {

private final String action;
private final String index;

/**
* Default constructor
*/
public SecurePluginRequest(String action, String index) {
this.action = action;
this.index = index;
}

public SecurePluginRequest(StreamInput in) throws IOException {
this.action = in.readString();
this.index = in.readString();
}

@Override
public void writeTo(final StreamOutput out) throws IOException {
out.writeString(action);
out.writeString(index);
}

@Override
public ActionRequestValidationException validate() {
return null;
}

public String getAction() {
return this.action;
}

public String getIndex() {
return this.index;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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.secure.actions.rest.create;

import java.io.IOException;

import org.opensearch.core.action.ActionResponse;
import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.core.common.io.stream.StreamOutput;
import org.opensearch.core.xcontent.ToXContentObject;
import org.opensearch.core.xcontent.XContentBuilder;

/**
* Response to a CreateSampleResourceRequest
*/
public class SecurePluginResponse extends ActionResponse implements ToXContentObject {
private final String message;

/**
* Default constructor
*
* @param message The message
*/
public SecurePluginResponse(String message) {
this.message = message;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(message);
}

/**
* Constructor with StreamInput
*
* @param in the stream input
*/
public SecurePluginResponse(final StreamInput in) throws IOException {
message = in.readString();
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("message", message);
builder.endObject();
return builder;
}
}
Loading
Loading