diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index add98ca572..0c77cf43f6 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -13,12 +13,14 @@ import java.util.Map; import com.carrotsearch.randomizedtesting.annotations.ThreadLeakScope; +import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; import org.opensearch.core.rest.RestStatus; -import org.opensearch.security.http.ExampleSystemIndexPlugin; +import org.opensearch.security.plugin.SystemIndexPlugin1; +import org.opensearch.security.plugin.SystemIndexPlugin2; import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain; import org.opensearch.test.framework.cluster.ClusterManager; import org.opensearch.test.framework.cluster.LocalCluster; @@ -26,7 +28,10 @@ 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.security.plugin.SystemIndexPlugin1.SYSTEM_INDEX_1; +import static org.opensearch.security.plugin.SystemIndexPlugin2.SYSTEM_INDEX_2; import static org.opensearch.security.support.ConfigConstants.SECURITY_RESTAPI_ROLES_ENABLED; import static org.opensearch.security.support.ConfigConstants.SECURITY_SYSTEM_INDICES_ENABLED_KEY; import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; @@ -39,11 +44,11 @@ public class SystemIndexTests { 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) + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.DEFAULT) .anonymousAuth(false) .authc(AUTHC_DOMAIN) .users(USER_ADMIN) - .plugin(ExampleSystemIndexPlugin.class) + .plugin(SystemIndexPlugin1.class, SystemIndexPlugin2.class) .nodeSettings( Map.of( SECURITY_RESTAPI_ROLES_ENABLED, @@ -54,6 +59,14 @@ public class SystemIndexTests { ) .build(); + @Before + public void wipeAllIndices() { + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + client.delete(".system-index1"); + client.delete(".system-index2"); + } + } + @Test public void adminShouldNotBeAbleToDeleteSecurityIndex() { try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { @@ -69,7 +82,7 @@ public void adminShouldNotBeAbleToDeleteSecurityIndex() { assertThat(response2.getStatusCode(), equalTo(RestStatus.OK.getStatus())); - // regular use can create system index + // regular user can create system index HttpResponse response3 = client.put(".system-index1"); assertThat(response3.getStatusCode(), equalTo(RestStatus.OK.getStatus())); @@ -81,6 +94,101 @@ public void adminShouldNotBeAbleToDeleteSecurityIndex() { } } + @Test + public void testPluginShouldBeAbleToIndexDocumentIntoItsSystemIndex() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.put("try-create-and-index/" + SYSTEM_INDEX_1); + + assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); + assertThat(response.getBody(), containsString(SystemIndexPlugin1.class.getCanonicalName())); + } + } + + @Test + public void testPluginShouldNotBeAbleToIndexDocumentIntoSystemIndexRegisteredByOtherPlugin() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.put("try-create-and-index/" + SYSTEM_INDEX_2); + + assertThat(response.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); + assertThat( + response.getBody(), + containsString( + "no permissions for [indices:admin/create] and User [name=plugin:org.opensearch.security.plugin.SystemIndexPlugin1" + ) + ); + } + } + + @Test + public void testPluginShouldBeAbleToCreateSystemIndexButUserShouldNotBeAbleToIndex() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.put("try-create-and-index/" + SYSTEM_INDEX_1 + "?runAs=user"); + + assertThat(response.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); + assertThat(response.getBody(), containsString("no permissions for [indices:data/write/index] and User [name=admin")); + } + } + + @Test + public void testPluginShouldNotBeAbleToRunClusterActions() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.get("try-cluster-health/plugin"); + + assertThat(response.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); + assertThat( + response.getBody(), + containsString( + "no permissions for [cluster:monitor/health] and User [name=plugin:org.opensearch.security.plugin.SystemIndexPlugin1" + ) + ); + } + } + + @Test + public void testAdminUserShouldBeAbleToRunClusterActions() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.get("try-cluster-health/user"); + + assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); + } + } + + @Test + public void testAuthenticatedUserShouldBeAbleToRunClusterActions() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.get("try-cluster-health/default"); + + assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); + } + } + + @Test + public void testPluginShouldBeAbleToBulkIndexDocumentIntoItsSystemIndex() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.put("try-create-and-bulk-index/" + SYSTEM_INDEX_1); + + assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); + } + } + + @Test + public void testPluginShouldNotBeAbleToBulkIndexDocumentIntoMixOfSystemIndexWhereAtLeastOneDoesNotBelongToPlugin() { + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + client.put(".system-index1"); + client.put(".system-index2"); + } + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.put("try-create-and-bulk-mixed-index"); + + assertThat( + response.getBody(), + containsString( + "no permissions for [indices:data/write/bulk[s]] and User [name=plugin:org.opensearch.security.plugin.SystemIndexPlugin1" + ) + ); + } + } + @Test public void regularUserShouldGetNoResultsWhenSearchingSystemIndex() { // Create system index and index a dummy document as the super admin user, data returned to super admin diff --git a/src/integrationTest/java/org/opensearch/security/http/ExampleSystemIndexPlugin.java b/src/integrationTest/java/org/opensearch/security/http/ExampleSystemIndexPlugin.java deleted file mode 100644 index b4877aae14..0000000000 --- a/src/integrationTest/java/org/opensearch/security/http/ExampleSystemIndexPlugin.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.security.http; - -import java.util.Collection; -import java.util.Collections; - -import org.opensearch.common.settings.Settings; -import org.opensearch.indices.SystemIndexDescriptor; -import org.opensearch.plugins.Plugin; -import org.opensearch.plugins.SystemIndexPlugin; - -public class ExampleSystemIndexPlugin extends Plugin implements SystemIndexPlugin { - - @Override - public Collection getSystemIndexDescriptors(Settings settings) { - final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(".system-index1", "System index 1"); - return Collections.singletonList(systemIndexDescriptor); - } -} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexAction.java new file mode 100644 index 0000000000..8621f2a609 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexAction.java @@ -0,0 +1,22 @@ +/* + * 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.security.plugin; + +import org.opensearch.action.ActionType; + +public class IndexDocumentIntoSystemIndexAction extends ActionType { + public static final IndexDocumentIntoSystemIndexAction INSTANCE = new IndexDocumentIntoSystemIndexAction(); + public static final String NAME = "mock:systemindex/index"; + + private IndexDocumentIntoSystemIndexAction() { + super(NAME, IndexDocumentIntoSystemIndexResponse::new); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java new file mode 100644 index 0000000000..d1b644ad11 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java @@ -0,0 +1,48 @@ +/* + * 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.security.plugin; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; + +public class IndexDocumentIntoSystemIndexRequest extends ActionRequest { + + private final String indexName; + + private final String runAs; + + public IndexDocumentIntoSystemIndexRequest(String indexName, String runAs) { + this.indexName = indexName; + this.runAs = runAs; + } + + public IndexDocumentIntoSystemIndexRequest(StreamInput in) throws IOException { + super(in); + this.indexName = in.readString(); + this.runAs = in.readOptionalString(); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getIndexName() { + return this.indexName; + } + + public String getRunAs() { + return this.runAs; + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java new file mode 100644 index 0000000000..9779cca252 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java @@ -0,0 +1,48 @@ +/* + * 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.security.plugin; + +// CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here +import java.io.IOException; + +import org.opensearch.action.support.master.AcknowledgedResponse; +import org.opensearch.core.common.io.stream.StreamInput; +import org.opensearch.core.common.io.stream.StreamOutput; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.core.xcontent.ToXContentObject; +import org.opensearch.core.xcontent.XContentBuilder; +// CS-ENFORCE-SINGLE + +public class IndexDocumentIntoSystemIndexResponse extends AcknowledgedResponse implements ToXContentObject { + + private String plugin; + + public IndexDocumentIntoSystemIndexResponse(boolean status, String plugin) { + super(status); + this.plugin = plugin; + } + + public IndexDocumentIntoSystemIndexResponse(StreamInput in) throws IOException { + super(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(plugin); + } + + @Override + public void addCustomFields(XContentBuilder builder, ToXContent.Params params) throws IOException { + super.addCustomFields(builder, params); + builder.field("plugin", plugin); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java new file mode 100644 index 0000000000..0d1dc4fe01 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java @@ -0,0 +1,78 @@ +/* + * 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.security.plugin; + +import java.util.List; + +import org.opensearch.action.bulk.BulkRequest; +import org.opensearch.action.bulk.BulkRequestBuilder; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.client.node.NodeClient; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestRequest; +import org.opensearch.security.identity.PluginContextSwitcher; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.PUT; +import static org.opensearch.security.plugin.SystemIndexPlugin1.SYSTEM_INDEX_1; +import static org.opensearch.security.plugin.SystemIndexPlugin2.SYSTEM_INDEX_2; + +public class RestBulkIndexDocumentIntoMixOfSystemIndexAction extends BaseRestHandler { + + private final Client client; + private final PluginContextSwitcher contextSwitcher; + + public RestBulkIndexDocumentIntoMixOfSystemIndexAction(Client client, PluginContextSwitcher contextSwitcher) { + this.client = client; + this.contextSwitcher = contextSwitcher; + } + + @Override + public List routes() { + return singletonList(new Route(PUT, "/try-create-and-bulk-mixed-index")); + } + + @Override + public String getName() { + return "test_bulk_index_document_into_mix_of_system_index_action"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + return new RestChannelConsumer() { + + @Override + public void accept(RestChannel channel) throws Exception { + contextSwitcher.runAs(() -> { + BulkRequestBuilder builder = client.prepareBulk(); + builder.add(new IndexRequest(SYSTEM_INDEX_1).source("{\"content\":1}", XContentType.JSON)); + builder.add(new IndexRequest(SYSTEM_INDEX_2).source("{\"content\":1}", XContentType.JSON)); + builder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + BulkRequest bulkRequest = builder.request(); + client.bulk(bulkRequest, ActionListener.wrap(r -> { + channel.sendResponse( + new BytesRestResponse(RestStatus.OK, r.toXContent(channel.newBuilder(), ToXContent.EMPTY_PARAMS)) + ); + }, fr -> { channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, String.valueOf(fr))); })); + return null; + }); + } + }; + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java new file mode 100644 index 0000000000..56fa3ef2a9 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java @@ -0,0 +1,80 @@ +/* + * 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.security.plugin; + +import java.util.List; + +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.bulk.BulkRequest; +import org.opensearch.action.bulk.BulkRequestBuilder; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.client.node.NodeClient; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestRequest; +import org.opensearch.security.identity.PluginContextSwitcher; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.PUT; + +public class RestBulkIndexDocumentIntoSystemIndexAction extends BaseRestHandler { + + private final Client client; + private final PluginContextSwitcher contextSwitcher; + + public RestBulkIndexDocumentIntoSystemIndexAction(Client client, PluginContextSwitcher contextSwitcher) { + this.client = client; + this.contextSwitcher = contextSwitcher; + } + + @Override + public List routes() { + return singletonList(new Route(PUT, "/try-create-and-bulk-index/{index}")); + } + + @Override + public String getName() { + return "test_bulk_index_document_into_system_index_action"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + String indexName = request.param("index"); + return new RestChannelConsumer() { + + @Override + public void accept(RestChannel channel) throws Exception { + contextSwitcher.runAs(() -> { + client.admin().indices().create(new CreateIndexRequest(indexName), ActionListener.wrap(r -> { + BulkRequestBuilder builder = client.prepareBulk(); + builder.add(new IndexRequest(indexName).source("{\"content\":1}", XContentType.JSON)); + builder.add(new IndexRequest(indexName).source("{\"content\":2}", XContentType.JSON)); + builder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + BulkRequest bulkRequest = builder.request(); + client.bulk(bulkRequest, ActionListener.wrap(r2 -> { + channel.sendResponse( + new BytesRestResponse(RestStatus.OK, r.toXContent(channel.newBuilder(), ToXContent.EMPTY_PARAMS)) + ); + }, fr -> { channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, String.valueOf(fr))); })); + }, fr -> { channel.sendResponse(new BytesRestResponse(RestStatus.FORBIDDEN, String.valueOf(fr))); })); + return null; + }); + } + }; + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java new file mode 100644 index 0000000000..e668e8bccc --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java @@ -0,0 +1,49 @@ +/* + * 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.security.plugin; + +import java.util.List; + +import org.opensearch.client.Client; +import org.opensearch.client.node.NodeClient; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.PUT; + +public class RestIndexDocumentIntoSystemIndexAction extends BaseRestHandler { + + private final Client client; + + public RestIndexDocumentIntoSystemIndexAction(Client client) { + this.client = client; + } + + @Override + public List routes() { + return singletonList(new Route(PUT, "/try-create-and-index/{index}")); + } + + @Override + public String getName() { + return "test_index_document_into_system_index_action"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + String runAs = request.param("runAs"); + String indexName = request.param("index"); + IndexDocumentIntoSystemIndexRequest indexRequest = new IndexDocumentIntoSystemIndexRequest(indexName, runAs); + return channel -> client.execute(IndexDocumentIntoSystemIndexAction.INSTANCE, indexRequest, new RestToXContentListener<>(channel)); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java new file mode 100644 index 0000000000..755f3278f0 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java @@ -0,0 +1,51 @@ +/* + * 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.security.plugin; + +import java.util.List; + +import org.opensearch.client.Client; +import org.opensearch.client.node.NodeClient; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; +import org.opensearch.security.identity.PluginContextSwitcher; + +import static java.util.Collections.singletonList; +import static org.opensearch.rest.RestRequest.Method.GET; + +public class RestRunClusterHealthAction extends BaseRestHandler { + + private final Client client; + private final PluginContextSwitcher contextSwitcher; + + public RestRunClusterHealthAction(Client client, PluginContextSwitcher contextSwitcher) { + this.client = client; + this.contextSwitcher = contextSwitcher; + } + + @Override + public List routes() { + return singletonList(new Route(GET, "/try-cluster-health/{runAs}")); + } + + @Override + public String getName() { + return "test_run_cluster_health_action"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + String runAs = request.param("runAs"); + RunClusterHealthRequest runRequest = new RunClusterHealthRequest(runAs); + return channel -> client.execute(RunClusterHealthAction.INSTANCE, runRequest, new RestToXContentListener<>(channel)); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthAction.java new file mode 100644 index 0000000000..b819465a35 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthAction.java @@ -0,0 +1,22 @@ +/* + * 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.security.plugin; + +import org.opensearch.action.ActionType; + +public class RunClusterHealthAction extends ActionType { + public static final RunClusterHealthAction INSTANCE = new RunClusterHealthAction(); + public static final String NAME = "mock:cluster/monitor/health"; + + private RunClusterHealthAction() { + super(NAME, RunClusterHealthResponse::new); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthRequest.java b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthRequest.java new file mode 100644 index 0000000000..8ae08bd6ff --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthRequest.java @@ -0,0 +1,40 @@ +/* + * 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.security.plugin; + +import java.io.IOException; + +import org.opensearch.action.ActionRequest; +import org.opensearch.action.ActionRequestValidationException; +import org.opensearch.core.common.io.stream.StreamInput; + +public class RunClusterHealthRequest extends ActionRequest { + + private final String runAs; + + public RunClusterHealthRequest(String runAs) { + this.runAs = runAs; + } + + public RunClusterHealthRequest(StreamInput in) throws IOException { + super(in); + this.runAs = in.readString(); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getRunAs() { + return this.runAs; + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthResponse.java b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthResponse.java new file mode 100644 index 0000000000..7a855744dc --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthResponse.java @@ -0,0 +1,42 @@ +/* + * 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.security.plugin; + +// CS-SUPPRESS-SINGLE: RegexpSingleline It is not possible to use phrase "cluster manager" instead of master here +import java.io.IOException; + +import org.opensearch.action.support.master.AcknowledgedResponse; +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; +// CS-ENFORCE-SINGLE + +public class RunClusterHealthResponse extends AcknowledgedResponse implements ToXContentObject { + + public RunClusterHealthResponse(boolean status) { + super(status); + } + + public RunClusterHealthResponse(StreamInput in) throws IOException { + super(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + } + + @Override + public void addCustomFields(XContentBuilder builder, Params params) throws IOException { + super.addCustomFields(builder, params); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java new file mode 100644 index 0000000000..3f1112c4b6 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -0,0 +1,110 @@ +/* + * 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.security.plugin; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Supplier; + +import org.opensearch.action.ActionRequest; +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.node.DiscoveryNodes; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.ClusterSettings; +import org.opensearch.common.settings.IndexScopedSettings; +import org.opensearch.common.settings.Settings; +import org.opensearch.common.settings.SettingsFilter; +import org.opensearch.core.action.ActionResponse; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +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.IdentityAwarePlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.SystemIndexPlugin; +import org.opensearch.repositories.RepositoriesService; +import org.opensearch.rest.RestController; +import org.opensearch.rest.RestHandler; +import org.opensearch.script.ScriptService; +import org.opensearch.security.identity.PluginContextSwitcher; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; + +public class SystemIndexPlugin1 extends Plugin implements SystemIndexPlugin, IdentityAwarePlugin { + public static final String SYSTEM_INDEX_1 = ".system-index1"; + + private PluginContextSwitcher contextSwitcher; + + private Client client; + + @Override + public Collection createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { + this.client = client; + this.contextSwitcher = new PluginContextSwitcher(); + return List.of(contextSwitcher); + } + + @Override + public Collection getSystemIndexDescriptors(Settings settings) { + final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(SYSTEM_INDEX_1, "System index 1"); + return Collections.singletonList(systemIndexDescriptor); + } + + @Override + public List getRestHandlers( + Settings settings, + RestController restController, + ClusterSettings clusterSettings, + IndexScopedSettings indexScopedSettings, + SettingsFilter settingsFilter, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier nodesInCluster + ) { + return List.of( + new RestIndexDocumentIntoSystemIndexAction(client), + new RestRunClusterHealthAction(client, contextSwitcher), + new RestBulkIndexDocumentIntoSystemIndexAction(client, contextSwitcher), + new RestBulkIndexDocumentIntoMixOfSystemIndexAction(client, contextSwitcher) + ); + } + + @Override + public List> getActions() { + return Arrays.asList( + new ActionHandler<>(IndexDocumentIntoSystemIndexAction.INSTANCE, TransportIndexDocumentIntoSystemIndexAction.class), + new ActionHandler<>(RunClusterHealthAction.INSTANCE, TransportRunClusterHealthAction.class) + ); + } + + @Override + public void assignSubject(PluginSubject pluginSystemSubject) { + if (contextSwitcher != null) { + this.contextSwitcher.initialize(pluginSystemSubject); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin2.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin2.java new file mode 100644 index 0000000000..8fcf23e3db --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin2.java @@ -0,0 +1,61 @@ +/* + * 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.security.plugin; + +import java.util.Collection; +import java.util.Collections; +import java.util.function.Supplier; + +import org.opensearch.client.Client; +import org.opensearch.cluster.metadata.IndexNameExpressionResolver; +import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.settings.Settings; +import org.opensearch.core.common.io.stream.NamedWriteableRegistry; +import org.opensearch.core.xcontent.NamedXContentRegistry; +import org.opensearch.env.Environment; +import org.opensearch.env.NodeEnvironment; +import org.opensearch.indices.SystemIndexDescriptor; +import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.SystemIndexPlugin; +import org.opensearch.repositories.RepositoriesService; +import org.opensearch.script.ScriptService; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.watcher.ResourceWatcherService; + +public class SystemIndexPlugin2 extends Plugin implements SystemIndexPlugin { + public static final String SYSTEM_INDEX_2 = ".system-index2"; + + private Client client; + + @Override + public Collection createComponents( + Client client, + ClusterService clusterService, + ThreadPool threadPool, + ResourceWatcherService resourceWatcherService, + ScriptService scriptService, + NamedXContentRegistry xContentRegistry, + Environment environment, + NodeEnvironment nodeEnvironment, + NamedWriteableRegistry namedWriteableRegistry, + IndexNameExpressionResolver indexNameExpressionResolver, + Supplier repositoriesServiceSupplier + ) { + this.client = client; + return Collections.emptyList(); + } + + @Override + public Collection getSystemIndexDescriptors(Settings settings) { + final SystemIndexDescriptor systemIndexDescriptor = new SystemIndexDescriptor(SYSTEM_INDEX_2, "System index 2"); + return Collections.singletonList(systemIndexDescriptor); + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java new file mode 100644 index 0000000000..c549da7809 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java @@ -0,0 +1,97 @@ +/* + * 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.security.plugin; + +import org.opensearch.action.admin.indices.create.CreateIndexRequest; +import org.opensearch.action.index.IndexRequest; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.action.support.WriteRequest; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.xcontent.XContentType; +import org.opensearch.core.action.ActionListener; +import org.opensearch.identity.IdentityService; +import org.opensearch.identity.Subject; +import org.opensearch.security.identity.PluginContextSwitcher; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; +import org.opensearch.tasks.Task; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; + +public class TransportIndexDocumentIntoSystemIndexAction extends HandledTransportAction< + IndexDocumentIntoSystemIndexRequest, + IndexDocumentIntoSystemIndexResponse> { + + private final Client client; + private final ThreadPool threadPool; + private final PluginContextSwitcher contextSwitcher; + private final IdentityService identityService; + + @Inject + public TransportIndexDocumentIntoSystemIndexAction( + final TransportService transportService, + final ActionFilters actionFilters, + final Client client, + final ThreadPool threadPool, + final PluginContextSwitcher contextSwitcher, + final IdentityService identityService + ) { + super(IndexDocumentIntoSystemIndexAction.NAME, transportService, actionFilters, IndexDocumentIntoSystemIndexRequest::new); + this.client = client; + this.threadPool = threadPool; + this.contextSwitcher = contextSwitcher; + this.identityService = identityService; + } + + @Override + protected void doExecute( + Task task, + IndexDocumentIntoSystemIndexRequest request, + ActionListener actionListener + ) { + String indexName = request.getIndexName(); + String runAs = request.getRunAs(); + Subject userSubject = identityService.getCurrentSubject(); + try { + contextSwitcher.runAs(() -> { + client.admin().indices().create(new CreateIndexRequest(indexName), ActionListener.wrap(r -> { + if ("user".equalsIgnoreCase(runAs)) { + userSubject.runAs(() -> { + client.index( + new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON), + ActionListener.wrap(r2 -> { + User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, user.getName())); + }, actionListener::onFailure) + ); + return null; + }); + } else { + client.index( + new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON), + ActionListener.wrap(r2 -> { + User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, user.getName())); + }, actionListener::onFailure) + ); + } + }, actionListener::onFailure)); + return null; + }); + } catch (Exception ex) { + throw new RuntimeException("Unexpected error: " + ex.getMessage()); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.java new file mode 100644 index 0000000000..4be06933a2 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.java @@ -0,0 +1,84 @@ +/* + * 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.security.plugin; + +import org.opensearch.action.admin.cluster.health.ClusterHealthRequest; +import org.opensearch.action.admin.cluster.health.ClusterHealthResponse; +import org.opensearch.action.support.ActionFilters; +import org.opensearch.action.support.HandledTransportAction; +import org.opensearch.client.Client; +import org.opensearch.common.inject.Inject; +import org.opensearch.core.action.ActionListener; +import org.opensearch.identity.IdentityService; +import org.opensearch.identity.Subject; +import org.opensearch.security.identity.PluginContextSwitcher; +import org.opensearch.tasks.Task; +import org.opensearch.threadpool.ThreadPool; +import org.opensearch.transport.TransportService; + +public class TransportRunClusterHealthAction extends HandledTransportAction { + + private final Client client; + private final ThreadPool threadPool; + private final PluginContextSwitcher contextSwitcher; + private final IdentityService identityService; + + @Inject + public TransportRunClusterHealthAction( + final TransportService transportService, + final ActionFilters actionFilters, + final Client client, + final ThreadPool threadPool, + final PluginContextSwitcher contextSwitcher, + final IdentityService identityService + ) { + super(RunClusterHealthAction.NAME, transportService, actionFilters, RunClusterHealthRequest::new); + this.client = client; + this.threadPool = threadPool; + this.contextSwitcher = contextSwitcher; + this.identityService = identityService; + } + + @Override + protected void doExecute(Task task, RunClusterHealthRequest request, ActionListener actionListener) { + String runAs = request.getRunAs(); + if ("user".equalsIgnoreCase(runAs)) { + Subject user = identityService.getCurrentSubject(); + try { + user.runAs(() -> { + ActionListener chr = ActionListener.wrap( + r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, + actionListener::onFailure + ); + client.admin().cluster().health(new ClusterHealthRequest(), chr); + return null; + }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } else if ("plugin".equalsIgnoreCase(runAs)) { + contextSwitcher.runAs(() -> { + ActionListener chr = ActionListener.wrap( + r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, + actionListener::onFailure + ); + client.admin().cluster().health(new ClusterHealthRequest(), chr); + return null; + }); + } else { + ActionListener chr = ActionListener.wrap( + r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, + actionListener::onFailure + ); + client.admin().cluster().health(new ClusterHealthRequest(), chr); + } + } +} diff --git a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java index d2c53c1de7..3571c0cd85 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -413,10 +413,11 @@ public Builder nodeSpecificSettings(int nodeNumber, Map settings } /** - * Adds additional plugins to the cluster - */ - public Builder plugin(Class plugin) { - this.plugins.add(plugin); + * Adds additional plugins to the cluster + */ + @SafeVarargs + public final Builder plugin(Class... plugins) { + this.plugins.addAll(List.of(plugins)); return this; } diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 9076b1da1f..1eccd5bc2f 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -111,7 +111,6 @@ import org.opensearch.http.netty4.ssl.SecureNetty4HttpServerTransport; import org.opensearch.identity.PluginSubject; import org.opensearch.identity.Subject; -import org.opensearch.identity.noop.NoopSubject; import org.opensearch.index.IndexModule; import org.opensearch.index.cache.query.QueryCache; import org.opensearch.indices.IndicesService; @@ -166,7 +165,7 @@ import org.opensearch.security.hasher.PasswordHasherFactory; import org.opensearch.security.http.NonSslHttpServerTransport; import org.opensearch.security.http.XFFResolver; -import org.opensearch.security.identity.NoopPluginSubject; +import org.opensearch.security.identity.ContextProvidingPluginSubject; import org.opensearch.security.identity.SecurityTokenManager; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.privileges.PrivilegesInterceptor; @@ -221,6 +220,7 @@ import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.ENDPOINTS_WITH_PERMISSIONS; import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.SECURITY_CONFIG_UPDATE; import static org.opensearch.security.setting.DeprecatedSettings.checkForDeprecatedSetting; +import static org.opensearch.security.support.ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER; import static org.opensearch.security.support.ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_SECURITYINDEX; import static org.opensearch.security.support.ConfigConstants.SECURITY_ALLOW_DEFAULT_INIT_USE_CLUSTER_STATE; import static org.opensearch.security.support.ConfigConstants.SECURITY_UNSUPPORTED_RESTAPI_ALLOW_SECURITYCONFIG_MODIFICATION; @@ -2149,8 +2149,7 @@ private static String handleKeyword(final String field) { @Override public Subject getCurrentSubject() { - // Not supported - return new NoopSubject(); + return (Subject) threadPool.getThreadContext().getPersistent(OPENDISTRO_SECURITY_AUTHENTICATED_USER); } @Override @@ -2160,7 +2159,7 @@ public SecurityTokenManager getTokenManager() { @Override public PluginSubject getPluginSubject(Plugin plugin) { - return new NoopPluginSubject(threadPool); + return new ContextProvidingPluginSubject(threadPool, settings, plugin); } @Override diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index 0b00bcf943..5be7e9622a 100644 --- a/src/main/java/org/opensearch/security/auth/BackendRegistry.java +++ b/src/main/java/org/opensearch/security/auth/BackendRegistry.java @@ -55,6 +55,7 @@ import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.common.transport.TransportAddress; import org.opensearch.core.rest.RestStatus; +import org.opensearch.identity.UserSubject; import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.auth.blocking.ClientBlockRegistry; import org.opensearch.security.auth.internal.NoOpAuthenticationBackend; @@ -223,7 +224,10 @@ public boolean authenticate(final SecurityRequestChannel request) { if (adminDns.isAdminDN(sslPrincipal)) { // PKI authenticated REST call - threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, new User(sslPrincipal)); + User superuser = new User(sslPrincipal); + UserSubject subject = new SecurityUser(threadPool, superuser); + threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, subject); + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, superuser); auditLog.logSucceededLogin(sslPrincipal, true, null, request); return true; } @@ -389,6 +393,8 @@ public boolean authenticate(final SecurityRequestChannel request) { final User impersonatedUser = impersonate(request, authenticatedUser); threadPool.getThreadContext() .putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, impersonatedUser == null ? authenticatedUser : impersonatedUser); + UserSubject subject = new SecurityUser(threadPool, impersonatedUser == null ? authenticatedUser : impersonatedUser); + threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, subject); auditLog.logSucceededLogin( (impersonatedUser == null ? authenticatedUser : impersonatedUser).getName(), false, @@ -421,7 +427,10 @@ public boolean authenticate(final SecurityRequestChannel request) { User anonymousUser = new User(User.ANONYMOUS.getName(), new HashSet(User.ANONYMOUS.getRoles()), null); anonymousUser.setRequestedTenant(tenant); + UserSubject subject = new SecurityUser(threadPool, anonymousUser); + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, anonymousUser); + threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, subject); auditLog.logSucceededLogin(anonymousUser.getName(), false, null, request); if (isDebugEnabled) { log.debug("Anonymous User is authenticated"); diff --git a/src/main/java/org/opensearch/security/auth/SecurityUser.java b/src/main/java/org/opensearch/security/auth/SecurityUser.java new file mode 100644 index 0000000000..8ce4bfb3a1 --- /dev/null +++ b/src/main/java/org/opensearch/security/auth/SecurityUser.java @@ -0,0 +1,51 @@ +/* + * 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.security.auth; + +import java.security.Principal; +import java.util.concurrent.Callable; + +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.identity.NamedPrincipal; +import org.opensearch.identity.UserSubject; +import org.opensearch.identity.tokens.AuthToken; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; +import org.opensearch.threadpool.ThreadPool; + +public class SecurityUser implements UserSubject { + private final NamedPrincipal userPrincipal; + private final ThreadPool threadPool; + private final User user; + + SecurityUser(ThreadPool threadPool, User user) { + this.threadPool = threadPool; + this.user = user; + this.userPrincipal = new NamedPrincipal(user.getName()); + } + + @Override + public void authenticate(AuthToken authToken) { + // not implemented + } + + @Override + public Principal getPrincipal() { + return userPrincipal; + } + + @Override + public T runAs(Callable callable) throws Exception { + try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) { + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); + return callable.call(); + } + } +} diff --git a/src/main/java/org/opensearch/security/configuration/SystemIndexSearcherWrapper.java b/src/main/java/org/opensearch/security/configuration/SystemIndexSearcherWrapper.java index 8e89b60712..c8cc6885d2 100644 --- a/src/main/java/org/opensearch/security/configuration/SystemIndexSearcherWrapper.java +++ b/src/main/java/org/opensearch/security/configuration/SystemIndexSearcherWrapper.java @@ -167,7 +167,7 @@ protected final boolean isBlockedSystemIndexRequest() { } final TransportAddress caller = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); final Set mappedRoles = evaluator.mapRoles(user, caller); - final SecurityRoles securityRoles = evaluator.getSecurityRoles(mappedRoles); + final SecurityRoles securityRoles = evaluator.filterSecurityRolesFromCache(mappedRoles); return !securityRoles.isPermittedOnSystemIndex(index.getName()); } return true; diff --git a/src/main/java/org/opensearch/security/filter/SecurityFilter.java b/src/main/java/org/opensearch/security/filter/SecurityFilter.java index f0ab7bb487..9b3cda74b3 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityFilter.java @@ -320,6 +320,7 @@ private void ap if (Origin.LOCAL.toString().equals(threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)) && (interClusterRequest || HeaderHelper.isDirectRequest(threadContext)) && (injectedRoles == null) + && (user == null) && !enforcePrivilegesEvaluation) { chain.proceed(task, action, request, listener); diff --git a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java new file mode 100644 index 0000000000..7a096abf08 --- /dev/null +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java @@ -0,0 +1,50 @@ +/* + * 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.security.identity; + +import java.security.Principal; +import java.util.concurrent.Callable; + +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.identity.NamedPrincipal; +import org.opensearch.identity.PluginSubject; +import org.opensearch.plugins.Plugin; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; +import org.opensearch.threadpool.ThreadPool; + +public class ContextProvidingPluginSubject implements PluginSubject { + private final ThreadPool threadPool; + private final NamedPrincipal pluginPrincipal; + private final User pluginUser; + + public ContextProvidingPluginSubject(ThreadPool threadPool, Settings settings, Plugin plugin) { + super(); + this.threadPool = threadPool; + this.pluginPrincipal = new NamedPrincipal(plugin.getClass().getCanonicalName()); + // Convention for plugin username. Prefixed with 'plugin:'. ':' is forbidden from usernames, so this + // guarantees that a user with this username cannot be created by other means. + this.pluginUser = new User("plugin:" + pluginPrincipal.getName()); + } + + @Override + public Principal getPrincipal() { + return pluginPrincipal; + } + + @Override + public T runAs(Callable callable) throws Exception { + try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) { + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, pluginUser); + return callable.call(); + } + } +} diff --git a/src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java b/src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java new file mode 100644 index 0000000000..a16671fda4 --- /dev/null +++ b/src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java @@ -0,0 +1,35 @@ +/* + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +package org.opensearch.security.identity; + +import java.util.Objects; +import java.util.concurrent.Callable; + +import org.opensearch.identity.PluginSubject; + +public class PluginContextSwitcher { + private PluginSubject pluginSubject; + + public PluginContextSwitcher() {} + + public void initialize(PluginSubject pluginSubject) { + this.pluginSubject = pluginSubject; + } + + public T runAs(Callable callable) { + Objects.requireNonNull(pluginSubject); + try { + return pluginSubject.runAs(callable); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 199442ee03..1d904fc5bb 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -29,6 +29,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -89,6 +90,8 @@ import org.opensearch.security.resolver.IndexResolverReplacer.Resolved; import org.opensearch.security.securityconf.ConfigModel; import org.opensearch.security.securityconf.DynamicConfigModel; +import org.opensearch.security.securityconf.InMemorySecurityRoles; +import org.opensearch.security.securityconf.InMemorySecurityRolesV7; import org.opensearch.security.securityconf.SecurityRoles; import org.opensearch.security.securityconf.impl.DashboardSignInOption; import org.opensearch.security.support.ConfigConstants; @@ -142,6 +145,7 @@ public class PrivilegesEvaluator { private final PitPrivilegesEvaluator pitPrivilegesEvaluator; private DynamicConfigModel dcm; private final NamedXContentRegistry namedXContentRegistry; + private final Map pluginRoles; public PrivilegesEvaluator( final ClusterService clusterService, @@ -163,6 +167,7 @@ public PrivilegesEvaluator( this.threadContext = threadPool.getThreadContext(); this.privilegesInterceptor = privilegesInterceptor; + this.pluginRoles = new HashMap<>(); this.checkSnapshotRestoreWritePrivileges = settings.getAsBoolean( ConfigConstants.SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, @@ -189,17 +194,42 @@ public void onDynamicConfigModelChanged(DynamicConfigModel dcm) { this.dcm = dcm; } - public SecurityRoles getSecurityRoles(Set roles) { + public SecurityRoles getSecurityRoles(User user, Set roles) { + SecurityRoles securityRoles; + if (user.isPluginUser()) { + securityRoles = getSecurityRoleForPlugin(user.getName()); + } else { + securityRoles = filterSecurityRolesFromCache(roles); + + // Add the security roles for this user so that they can be used for DLS parameter substitution. + user.addSecurityRoles(roles); + setUserInfoInThreadContext(user); + } + + return securityRoles; + } + + public SecurityRoles filterSecurityRolesFromCache(Set roles) { return configModel.getSecurityRoles().filter(roles); } + public SecurityRoles getSecurityRoleForPlugin(String pluginIdentifier) { + InMemorySecurityRoles pluginRole = pluginRoles.get(pluginIdentifier); + if (pluginRole == null) { + pluginRole = new InMemorySecurityRolesV7(1); + pluginRole.addSecurityRole(pluginIdentifier, Set.of(BulkAction.NAME), Map.of()); + pluginRoles.put(pluginIdentifier, pluginRole); + } + return pluginRole; + } + public boolean hasRestAdminPermissions(final User user, final TransportAddress remoteAddress, final String permissions) { final Set userRoles = mapRoles(user, remoteAddress); return hasRestAdminPermissions(userRoles, permissions); } private boolean hasRestAdminPermissions(final Set roles, String permission) { - final SecurityRoles securityRoles = getSecurityRoles(roles); + final SecurityRoles securityRoles = filterSecurityRolesFromCache(roles); return securityRoles.hasExplicitClusterPermissionPermission(permission); } @@ -279,11 +309,7 @@ public PrivilegesEvaluatorResponse evaluate(PrivilegesEvaluationContext context) context.setMappedRoles(mappedRoles); } presponse.resolvedSecurityRoles.addAll(mappedRoles); - final SecurityRoles securityRoles = getSecurityRoles(mappedRoles); - - // Add the security roles for this user so that they can be used for DLS parameter substitution. - user.addSecurityRoles(mappedRoles); - setUserInfoInThreadContext(user); + final SecurityRoles securityRoles = getSecurityRoles(user, mappedRoles); final boolean isDebugEnabled = log.isDebugEnabled(); if (isDebugEnabled) { @@ -327,7 +353,7 @@ public PrivilegesEvaluatorResponse evaluate(PrivilegesEvaluationContext context) return presponse; } - // Security index access + // System index access if (systemIndexAccessEvaluator.evaluate( request, task, diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java index 915514264c..6523cf94a7 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluatorResponse.java @@ -47,6 +47,10 @@ public Set getMissingPrivileges() { return new HashSet(missingPrivileges); } + public boolean addMissingPrivileges(String action) { + return missingPrivileges.add(action); + } + public Set getMissingSecurityRoles() { return new HashSet<>(missingSecurityRoles); } diff --git a/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java b/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java index 23612e1a52..8a11e433e8 100644 --- a/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java @@ -109,6 +109,7 @@ public PrivilegesEvaluatorResponse evaluate( auditLog.logSecurityIndexAttempt(request, action, task); log.warn("{} for '{}' as source index is not allowed", action, securityIndex); presponse.allowed = false; + presponse.addMissingPrivileges(action); return presponse.markComplete(); } return presponse; diff --git a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java index 38825a9bf1..00a2791ba3 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -272,10 +272,12 @@ private void evaluateSystemIndicesAccess( log.debug("Service account cannot access regular indices: {}", regularIndices); } presponse.allowed = false; + presponse.addMissingPrivileges(action); presponse.markComplete(); return; } boolean containsProtectedIndex = requestContainsAnyProtectedSystemIndices(requestedResolved); + if (containsProtectedIndex) { auditLog.logSecurityIndexAttempt(request, action, task); if (log.isInfoEnabled()) { @@ -287,6 +289,7 @@ private void evaluateSystemIndicesAccess( ); } presponse.allowed = false; + presponse.addMissingPrivileges(action); presponse.markComplete(); return; } else if (containsSystemIndex @@ -307,11 +310,37 @@ private void evaluateSystemIndicesAccess( ); } presponse.allowed = false; + presponse.addMissingPrivileges(action); presponse.markComplete(); return; } } + if (user.isPluginUser()) { + Set matchingSystemIndices = SystemIndexRegistry.matchesPluginSystemIndexPattern( + user.getName().replace("plugin:", ""), + requestedResolved.getAllIndices() + ); + if (requestedResolved.getAllIndices().equals(matchingSystemIndices)) { + // plugin is authorized to perform any actions on its own registered system indices + presponse.allowed = true; + presponse.markComplete(); + } else { + if (log.isInfoEnabled()) { + log.info( + "Plugin {} can only perform {} on it's own registered System Indices. System indices from request that match plugin's registered system indices: {}", + user.getName(), + action, + matchingSystemIndices + ); + } + presponse.allowed = false; + presponse.addMissingPrivileges(action); + presponse.markComplete(); + } + return; + } + if (isActionAllowed(action)) { if (requestedResolved.isLocalAll()) { if (filterSecurityIndex) { @@ -329,6 +358,7 @@ private void evaluateSystemIndicesAccess( auditLog.logSecurityIndexAttempt(request, action, task); log.warn("{} for '_all' indices is not allowed for a regular user", action); presponse.allowed = false; + presponse.addMissingPrivileges(action); presponse.markComplete(); } } @@ -343,6 +373,7 @@ else if (containsSystemIndex && !isSystemIndexPermissionEnabled) { log.debug("Filtered '{}' but resulting list is empty", securityIndex); } presponse.allowed = false; + presponse.addMissingPrivileges(action); presponse.markComplete(); return; } @@ -355,6 +386,7 @@ else if (containsSystemIndex && !isSystemIndexPermissionEnabled) { final String foundSystemIndexes = String.join(", ", getAllSystemIndices(requestedResolved)); log.warn("{} for '{}' index is not allowed for a regular user", action, foundSystemIndexes); presponse.allowed = false; + presponse.addMissingPrivileges(action); presponse.markComplete(); } } diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index 29bc0c4426..4131bd6514 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -219,11 +219,11 @@ public static class SecurityRoles implements org.opensearch.security.securitycon final Set roles; - private SecurityRoles(int roleCount) { + protected SecurityRoles(int roleCount) { roles = new HashSet<>(roleCount); } - private SecurityRoles addSecurityRole(SecurityRole securityRole) { + protected SecurityRoles addSecurityRole(SecurityRole securityRole) { if (securityRole != null) { this.roles.add(securityRole); } @@ -536,7 +536,7 @@ public SecurityRole build() { } } - private SecurityRole(String name, Set ipatterns, WildcardMatcher clusterPerms) { + SecurityRole(String name, Set ipatterns, WildcardMatcher clusterPerms) { this.name = Objects.requireNonNull(name); this.ipatterns = ipatterns; this.clusterPerms = clusterPerms; diff --git a/src/main/java/org/opensearch/security/securityconf/InMemorySecurityRoles.java b/src/main/java/org/opensearch/security/securityconf/InMemorySecurityRoles.java new file mode 100644 index 0000000000..934533788f --- /dev/null +++ b/src/main/java/org/opensearch/security/securityconf/InMemorySecurityRoles.java @@ -0,0 +1,20 @@ +/* + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.securityconf; + +import java.util.Map; +import java.util.Set; + +public interface InMemorySecurityRoles extends SecurityRoles { + + void addSecurityRole(String roleName, Set clusterPerms, Map> indexPatternToAllowedActions); +} diff --git a/src/main/java/org/opensearch/security/securityconf/InMemorySecurityRolesV7.java b/src/main/java/org/opensearch/security/securityconf/InMemorySecurityRolesV7.java new file mode 100644 index 0000000000..da5a07298c --- /dev/null +++ b/src/main/java/org/opensearch/security/securityconf/InMemorySecurityRolesV7.java @@ -0,0 +1,37 @@ +/* + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.securityconf; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.opensearch.security.support.WildcardMatcher; + +public class InMemorySecurityRolesV7 extends ConfigModelV7.SecurityRoles implements InMemorySecurityRoles { + + public InMemorySecurityRolesV7(int roleCount) { + super(roleCount); + } + + @Override + public void addSecurityRole(String roleName, Set clusterPerms, Map> indexPatternToAllowedActions) { + Set ipatterns = new HashSet<>(); + for (Map.Entry> entry : indexPatternToAllowedActions.entrySet()) { + ConfigModelV7.IndexPattern idxPattern = new ConfigModelV7.IndexPattern(entry.getKey()); + idxPattern.addPerm(entry.getValue()); + ipatterns.add(idxPattern); + } + ConfigModelV7.SecurityRole role = new ConfigModelV7.SecurityRole(roleName, ipatterns, WildcardMatcher.from(clusterPerms)); + roles.add(role); + } +} diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index f35afc6489..aa38d9becf 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -113,6 +113,7 @@ public class ConfigConstants { public static final String OPENDISTRO_SECURITY_SSL_TRANSPORT_PRINCIPAL = OPENDISTRO_SECURITY_CONFIG_PREFIX + "ssl_transport_principal"; public static final String OPENDISTRO_SECURITY_USER = OPENDISTRO_SECURITY_CONFIG_PREFIX + "user"; + public static final String OPENDISTRO_SECURITY_AUTHENTICATED_USER = OPENDISTRO_SECURITY_CONFIG_PREFIX + "authenticated_user"; public static final String OPENDISTRO_SECURITY_USER_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX + "user_header"; public static final String OPENDISTRO_SECURITY_USER_INFO_THREAD_CONTEXT = OPENDISTRO_SECURITY_CONFIG_PREFIX + "user_info"; diff --git a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java index 9741014fda..099e025c31 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java +++ b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java @@ -129,6 +129,15 @@ public SecurityRequestHandler getHandler(String ); } + private User determineUser(Connection connection) { + User user0 = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + // pluginUser did not exist prior to 2.18.0 + if (user0 != null && user0.isPluginUser() && connection.getVersion().before(Version.V_2_18_0)) { + user0 = null; + } + return user0; + } + public void sendRequestDecorate( AsyncSender sender, Connection connection, @@ -139,7 +148,7 @@ public void sendRequestDecorate( DiscoveryNode localNode ) { final Map origHeaders0 = getThreadContext().getHeaders(); - final User user0 = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + final User user0 = determineUser(connection); final String injectedUserString = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER); final String injectedRolesString = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES); final String injectedRolesValidationString = getThreadContext().getTransient( @@ -320,7 +329,7 @@ && getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADE if (origUser != null) { // if request is going to be handled by same node, we directly put transient value as the thread context is not going to be - // stah. + // stashed. getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, origUser); } else if (StringUtils.isNotEmpty(injectedRolesString)) { getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, injectedRolesString); diff --git a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java index 18c0c21282..f8e2f56d98 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java +++ b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java @@ -29,7 +29,6 @@ // CS-SUPPRESS-SINGLE: RegexpSingleline Extensions manager used to allow/disallow TLS connections to extensions import java.net.InetSocketAddress; import java.security.cert.X509Certificate; -import java.util.Objects; import java.util.UUID; import java.util.stream.Collectors; @@ -181,10 +180,8 @@ protected void messageReceivedDecorate( getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, injectedUserHeader); } } else { - getThreadContext().putTransient( - ConfigConstants.OPENDISTRO_SECURITY_USER, - Objects.requireNonNull((User) Base64Helper.deserializeObject(userHeader, useJDKSerialization)) - ); + User deserializedUser = (User) Base64Helper.deserializeObject(userHeader, useJDKSerialization); + getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, deserializedUser); } String originalRemoteAddress = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER); diff --git a/src/main/java/org/opensearch/security/user/User.java b/src/main/java/org/opensearch/security/user/User.java index 6abba3d734..190729623d 100644 --- a/src/main/java/org/opensearch/security/user/User.java +++ b/src/main/java/org/opensearch/security/user/User.java @@ -290,10 +290,19 @@ public final Set getSecurityRoles() { /** * Check the custom attributes associated with this user * - * @return true if it has a service account attributes. otherwise false + * @return true if it has a service account attributes, otherwise false */ public boolean isServiceAccount() { Map userAttributesMap = this.getCustomAttributesMap(); return userAttributesMap != null && "true".equals(userAttributesMap.get("attr.internal.service")); } + + /** + * Check the custom attributes associated with this user + * + * @return true if it has a plugin account attributes, otherwise false + */ + public boolean isPluginUser() { + return name != null && name.startsWith("plugin:"); + } } diff --git a/src/test/java/org/opensearch/security/auth/SecurityUserTests.java b/src/test/java/org/opensearch/security/auth/SecurityUserTests.java new file mode 100644 index 0000000000..4ae15ef303 --- /dev/null +++ b/src/test/java/org/opensearch/security/auth/SecurityUserTests.java @@ -0,0 +1,54 @@ +/* + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.auth; + +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import org.opensearch.security.user.User; +import org.opensearch.threadpool.TestThreadPool; +import org.opensearch.threadpool.ThreadPool; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.security.support.ConfigConstants.OPENDISTRO_SECURITY_USER; +import static org.junit.Assert.assertNull; + +public class SecurityUserTests { + + public static boolean terminate(ThreadPool threadPool) { + return ThreadPool.terminate(threadPool, 10, TimeUnit.SECONDS); + } + + @Test + public void testSecurityUserSubjectRunAs() throws Exception { + final ThreadPool threadPool = new TestThreadPool(getClass().getName()); + + User user = new User("testUser"); + + SecurityUser subject = new SecurityUser(threadPool, user); + + assertThat(subject.getPrincipal().getName(), equalTo(user.getName())); + + assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); + + subject.runAs(() -> { + assertThat(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER), equalTo(user)); + return null; + }); + + assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); + + terminate(threadPool); + } +} diff --git a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java new file mode 100644 index 0000000000..48851c48b3 --- /dev/null +++ b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java @@ -0,0 +1,91 @@ +/* + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.security.identity; + +import org.junit.Test; + +import org.opensearch.common.settings.Settings; +import org.opensearch.plugins.IdentityAwarePlugin; +import org.opensearch.plugins.Plugin; +import org.opensearch.security.auth.SecurityUserTests; +import org.opensearch.security.user.User; +import org.opensearch.threadpool.TestThreadPool; +import org.opensearch.threadpool.ThreadPool; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.opensearch.security.support.ConfigConstants.OPENDISTRO_SECURITY_USER; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; + +public class ContextProvidingPluginSubjectTests { + static class TestIdentityAwarePlugin extends Plugin implements IdentityAwarePlugin { + + } + + @Test + public void testSecurityUserSubjectRunAs() throws Exception { + final ThreadPool threadPool = new TestThreadPool(getClass().getName()); + + final Plugin testPlugin = new TestIdentityAwarePlugin(); + + final User pluginUser = new User("plugin:" + testPlugin.getClass().getCanonicalName()); + + ContextProvidingPluginSubject subject = new ContextProvidingPluginSubject(threadPool, Settings.EMPTY, testPlugin); + + assertThat(subject.getPrincipal().getName(), equalTo(testPlugin.getClass().getCanonicalName())); + + assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); + + subject.runAs(() -> { + assertThat(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER), equalTo(pluginUser)); + return null; + }); + + assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); + + SecurityUserTests.terminate(threadPool); + } + + @Test + public void testPluginContextSwitcherRunAs() throws Exception { + final ThreadPool threadPool = new TestThreadPool(getClass().getName()); + + final Plugin testPlugin = new TestIdentityAwarePlugin(); + + final PluginContextSwitcher contextSwitcher = new PluginContextSwitcher(); + + final User pluginUser = new User("plugin:" + testPlugin.getClass().getCanonicalName()); + + ContextProvidingPluginSubject subject = new ContextProvidingPluginSubject(threadPool, Settings.EMPTY, testPlugin); + + contextSwitcher.initialize(subject); + + assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); + + subject.runAs(() -> { + assertThat(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER), equalTo(pluginUser)); + return null; + }); + + assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); + + SecurityUserTests.terminate(threadPool); + } + + @Test + public void testPluginContextSwitcherUninitializedRunAs() throws Exception { + final PluginContextSwitcher contextSwitcher = new PluginContextSwitcher(); + + assertThrows(NullPointerException.class, () -> contextSwitcher.runAs(() -> null)); + } +} diff --git a/src/test/java/org/opensearch/security/privileges/SystemIndexAccessEvaluatorTest.java b/src/test/java/org/opensearch/security/privileges/SystemIndexAccessEvaluatorTest.java index 58e811fa24..fcf67aa5a5 100644 --- a/src/test/java/org/opensearch/security/privileges/SystemIndexAccessEvaluatorTest.java +++ b/src/test/java/org/opensearch/security/privileges/SystemIndexAccessEvaluatorTest.java @@ -46,6 +46,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.opensearch.security.support.ConfigConstants.SYSTEM_INDEX_PERMISSION; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -151,6 +152,8 @@ public void setup( when(log.isDebugEnabled()).thenReturn(true); when(log.isInfoEnabled()).thenReturn(true); + when(presponse.addMissingPrivileges(any())).thenReturn(true); + doReturn(ImmutableSet.of(index)).when(ip).getResolvedIndexPattern(user, indexNameExpressionResolver, cs, true, false); } @@ -288,6 +291,7 @@ public void testUnprotectedActionOnSystemIndex_systemIndexPermissionEnabled_With verify(auditLog).logSecurityIndexAttempt(request, UNPROTECTED_ACTION, null); verify(log).isInfoEnabled(); verify(log).info("No {} permission for user roles {} to System Indices {}", UNPROTECTED_ACTION, securityRoles, TEST_SYSTEM_INDEX); + verify(presponse).addMissingPrivileges(UNPROTECTED_ACTION); } @Test @@ -442,6 +446,7 @@ public void testDisableCacheOrRealtimeOnSystemIndex_systemIndexPermissionEnabled ); verify(log).debug("Disable search request cache for this request"); verify(log).debug("Disable realtime for this request"); + verify(presponse, times(3)).addMissingPrivileges(UNPROTECTED_ACTION); } @Test @@ -495,6 +500,7 @@ public void testProtectedActionLocalAll_systemIndexDisabled() { verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); assertThat(presponse.allowed, is(false)); + verify(presponse).addMissingPrivileges(PROTECTED_ACTION); verify(presponse).markComplete(); verify(log).warn("{} for '_all' indices is not allowed for a regular user", "indices:data/write"); } @@ -509,6 +515,7 @@ public void testProtectedActionLocalAll_systemIndexPermissionDisabled() { verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); assertThat(presponse.allowed, is(false)); + verify(presponse).addMissingPrivileges(PROTECTED_ACTION); verify(presponse).markComplete(); verify(log).warn("{} for '_all' indices is not allowed for a regular user", PROTECTED_ACTION); } @@ -523,6 +530,7 @@ public void testProtectedActionLocalAll_systemIndexPermissionEnabled() { verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); assertThat(presponse.allowed, is(false)); + verify(presponse).addMissingPrivileges(PROTECTED_ACTION); verify(presponse).markComplete(); verify(log).warn("{} for '_all' indices is not allowed for a regular user", PROTECTED_ACTION); } @@ -581,6 +589,7 @@ public void testProtectedActionOnSystemIndex_systemIndexPermissionDisabled() { verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); assertThat(presponse.allowed, is(false)); + verify(presponse).addMissingPrivileges(PROTECTED_ACTION); verify(presponse).markComplete(); verify(log).warn("{} for '{}' index is not allowed for a regular user", PROTECTED_ACTION, TEST_SYSTEM_INDEX); } @@ -598,6 +607,7 @@ public void testProtectedActionOnSystemIndex_systemIndexPermissionEnabled_withou verify(presponse).markComplete(); verify(log).isInfoEnabled(); verify(log).info("No {} permission for user roles {} to System Indices {}", PROTECTED_ACTION, securityRoles, TEST_SYSTEM_INDEX); + verify(presponse).addMissingPrivileges(PROTECTED_ACTION); } @Test @@ -622,6 +632,7 @@ public void testProtectedActionOnProtectedSystemIndex_systemIndexDisabled() { verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); assertThat(presponse.allowed, is(false)); + verify(presponse).addMissingPrivileges(PROTECTED_ACTION); verify(presponse).markComplete(); verify(log).warn("{} for '{}' index is not allowed for a regular user", PROTECTED_ACTION, SECURITY_INDEX); @@ -637,6 +648,7 @@ public void testProtectedActionOnProtectedSystemIndex_systemIndexPermissionDisab verify(auditLog).logSecurityIndexAttempt(request, PROTECTED_ACTION, task); assertThat(presponse.allowed, is(false)); + verify(presponse).addMissingPrivileges(PROTECTED_ACTION); verify(presponse).markComplete(); verify(log).warn("{} for '{}' index is not allowed for a regular user", PROTECTED_ACTION, SECURITY_INDEX); @@ -672,6 +684,7 @@ private void testSecurityIndexAccess(String action) { verify(auditLog).logSecurityIndexAttempt(request, action, task); assertThat(presponse.allowed, is(false)); + verify(presponse).addMissingPrivileges(action); verify(presponse).markComplete(); verify(log).isInfoEnabled(); diff --git a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java index f21a3e98a2..a1455503ba 100644 --- a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java +++ b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java @@ -32,6 +32,7 @@ import java.util.Collection; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -59,6 +60,7 @@ import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.ENDPOINTS_WITH_PERMISSIONS; import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.RELOAD_CERTS_ACTION; import static org.opensearch.security.dlic.rest.api.RestApiAdminPrivilegesEvaluator.SECURITY_CONFIG_UPDATE; +import static org.junit.Assert.assertTrue; public class SecurityRolesPermissionsTest { @@ -221,6 +223,14 @@ public void hasExplicitClusterPermissionPermissionForRestAdmin() { assertHasNoPermissionsForRestApiAdminOnePermissionRole(Endpoint.CONFIG, securityConfigAllowRole); } + @Test + public void testCreateSecurityRole() { + InMemorySecurityRoles securityRoles = new InMemorySecurityRolesV7(1); + securityRoles.addSecurityRole("testRole", Set.of("cluster:monitor/health"), Map.of("*", Set.of("indices:data/read/search"))); + assertTrue(securityRoles.getRoleNames().contains("testRole")); + assertTrue(securityRoles.hasExplicitClusterPermissionPermission("cluster:monitor/health")); + } + void assertHasNoPermissionsForRestApiAdminOnePermissionRole(final Endpoint allowEndpoint, final SecurityRoles allowOnlyRoleForRole) { final Collection noPermissionEndpoints = ENDPOINTS_WITH_PERMISSIONS.keySet() .stream() diff --git a/src/test/java/org/opensearch/security/system_indices/AbstractSystemIndicesTests.java b/src/test/java/org/opensearch/security/system_indices/AbstractSystemIndicesTests.java index deb6f6f5e3..09aacec057 100644 --- a/src/test/java/org/opensearch/security/system_indices/AbstractSystemIndicesTests.java +++ b/src/test/java/org/opensearch/security/system_indices/AbstractSystemIndicesTests.java @@ -186,6 +186,7 @@ String permissionExceptionMessage(String action, String username) { } void validateForbiddenResponse(RestHelper.HttpResponse response, String action, String user) { + assertThat(response.getStatusCode(), is(RestStatus.FORBIDDEN.getStatus())); MatcherAssert.assertThat(response.getBody(), Matchers.containsStringIgnoringCase(permissionExceptionMessage(action, user))); } @@ -195,7 +196,7 @@ void shouldBeAllowedOnlyForAuthorizedIndices(String index, RestHelper.HttpRespon boolean isRequestingAccessToNonAuthorizedSystemIndex = (!user.equals(allAccessUser) && index.equals(SYSTEM_INDEX_WITH_NO_ASSOCIATED_ROLE_PERMISSIONS)); if (isSecurityIndexRequest || isRequestingAccessToNonAuthorizedSystemIndex) { - validateForbiddenResponse(response, isSecurityIndexRequest ? "" : action, user); + validateForbiddenResponse(response, action, user); } else { assertThat(response.getStatusCode(), is(RestStatus.OK.getStatus())); } diff --git a/src/test/java/org/opensearch/security/system_indices/SystemIndexDisabledTests.java b/src/test/java/org/opensearch/security/system_indices/SystemIndexDisabledTests.java index b387070195..7abe20046c 100644 --- a/src/test/java/org/opensearch/security/system_indices/SystemIndexDisabledTests.java +++ b/src/test/java/org/opensearch/security/system_indices/SystemIndexDisabledTests.java @@ -123,7 +123,7 @@ public void testDeleteAsSuperAdmin() { @Test public void testDeleteAsAdmin() { - testDeleteWithUser(allAccessUser, allAccessUserHeader, "", ""); + testDeleteWithUser(allAccessUser, allAccessUserHeader, "indices:admin/delete", "indices:data/write/delete"); } @Test @@ -175,7 +175,7 @@ public void testCloseOpenAsAdmin() { for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_close", "", allAccessUserHeader); - shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", allAccessUser); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "indices:admin/close", allAccessUser); // User can open the index but cannot close it response = restHelper.executePostRequest(index + "/_open", "", allAccessUserHeader); @@ -348,14 +348,14 @@ public void testSnapshotSystemIndicesAsAdmin() { assertThat(res.getStatusCode(), is(HttpStatus.SC_UNAUTHORIZED)); res = restHelper.executePostRequest(snapshotRequest + "/_restore?wait_for_completion=true", "", allAccessUserHeader); - shouldBeAllowedOnlyForAuthorizedIndices(index, res, "", allAccessUser); + shouldBeAllowedOnlyForAuthorizedIndices(index, res, "cluster:admin/snapshot/restore", allAccessUser); res = restHelper.executePostRequest( snapshotRequest + "/_restore?wait_for_completion=true", "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", allAccessUserHeader ); - shouldBeAllowedOnlyForAuthorizedIndices(index, res, "", allAccessUser); + shouldBeAllowedOnlyForAuthorizedIndices(index, res, "cluster:admin/snapshot/restore", allAccessUser); } } @@ -384,7 +384,9 @@ private void testSnapshotWithUser(String user, Header header) { RestHelper.HttpResponse res = restHelper.executeGetRequest(snapshotRequest); assertThat(res.getStatusCode(), is(HttpStatus.SC_UNAUTHORIZED)); - String action = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) ? "" : "indices:data/write/index, indices:admin/create"; + String action = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) + ? "cluster:admin/snapshot/restore" + : "indices:data/write/index, indices:admin/create"; res = restHelper.executePostRequest(snapshotRequest + "/_restore?wait_for_completion=true", "", header); shouldBeAllowedOnlyForAuthorizedIndices(index, res, action, user); diff --git a/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionDisabledTests.java b/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionDisabledTests.java index fcb1f9265c..562b3b6963 100644 --- a/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionDisabledTests.java +++ b/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionDisabledTests.java @@ -140,10 +140,10 @@ private void testDeleteWithUser(String user, Header header) { for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executeDeleteRequest(index + "/_doc/document1", header); - validateForbiddenResponse(response, "", user); + validateForbiddenResponse(response, "indices:data/write/delete", user); response = restHelper.executeDeleteRequest(index, header); - validateForbiddenResponse(response, "", user); + validateForbiddenResponse(response, "indices:admin/delete", user); } } @@ -169,7 +169,7 @@ public void testCloseOpenAsAdmin() { for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_close", "", allAccessUserHeader); - validateForbiddenResponse(response, "", allAccessUser); + validateForbiddenResponse(response, "indices:admin/close", allAccessUser); // admin cannot close any system index but can open them response = restHelper.executePostRequest(index + "/_open", "", allAccessUserHeader); @@ -192,7 +192,7 @@ private void testCloseOpenWithUser(String user, Header header) { for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_close", "", header); - validateForbiddenResponse(response, "", user); + validateForbiddenResponse(response, "indices:admin/close", user); // normal user cannot open or close security index response = restHelper.executePostRequest(index + "/_open", "", header); @@ -284,10 +284,10 @@ private void testUpdateWithUser(String user, Header header) { for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executePutRequest(index + "/_mapping", newMappings, header); - validateForbiddenResponse(response, "", user); + validateForbiddenResponse(response, "indices:admin/mapping/put", user); response = restHelper.executePutRequest(index + "/_settings", updateIndexSettings, header); - validateForbiddenResponse(response, "", user); + validateForbiddenResponse(response, "indices:admin/settings/update", user); } } @@ -346,14 +346,14 @@ public void testSnapshotSystemIndicesAsAdmin() { "", allAccessUserHeader ); - validateForbiddenResponse(res, "", allAccessUser); + validateForbiddenResponse(res, "cluster:admin/snapshot/restore", allAccessUser); res = restHelper.executePostRequest( "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", allAccessUserHeader ); - shouldBeAllowedOnlyForAuthorizedIndices(index, res, "", allAccessUser); + shouldBeAllowedOnlyForAuthorizedIndices(index, res, "cluster:admin/snapshot/restore", allAccessUser); } } @@ -382,7 +382,7 @@ private void testSnapshotSystemIndexWithUser(String user, Header header) { assertThat(res.getStatusCode(), is(HttpStatus.SC_UNAUTHORIZED)); res = restHelper.executePostRequest("_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", "", header); - validateForbiddenResponse(res, "", user); + validateForbiddenResponse(res, "cluster:admin/snapshot/restore", user); res = restHelper.executePostRequest( "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", @@ -390,7 +390,7 @@ private void testSnapshotSystemIndexWithUser(String user, Header header) { header ); if (index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN)) { - validateForbiddenResponse(res, "", user); + validateForbiddenResponse(res, "cluster:admin/snapshot/restore", user); } else { validateForbiddenResponse(res, "indices:data/write/index, indices:admin/create", user); } diff --git a/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionEnabledTests.java b/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionEnabledTests.java index 397b8c2286..714e5161bd 100644 --- a/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionEnabledTests.java +++ b/src/test/java/org/opensearch/security/system_indices/SystemIndexPermissionEnabledTests.java @@ -59,7 +59,7 @@ public void testSearchAsAdmin() { for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_search", matchAllQuery, allAccessUserHeader); // no system indices are searchable by admin - validateForbiddenResponse(response, "", allAccessUser); + validateForbiddenResponse(response, "indices:data/read/search", allAccessUser); } // search all indices @@ -78,7 +78,7 @@ public void testSearchAsNormalUser() throws Exception { // security index is only accessible by super-admin RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_search", "", normalUserHeader); if (index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) || index.equals(SYSTEM_INDEX_WITH_NO_ASSOCIATED_ROLE_PERMISSIONS)) { - validateForbiddenResponse(response, "", normalUser); + validateForbiddenResponse(response, "indices:data/read/search", normalUser); } else { // got 1 hits because system index permissions are enabled validateSearchResponse(response, 1); @@ -98,7 +98,7 @@ public void testSearchAsNormalUserWithoutSystemIndexAccess() { // search system indices for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_search", "", normalUserWithoutSystemIndexHeader); - validateForbiddenResponse(response, "", normalUserWithoutSystemIndex); + validateForbiddenResponse(response, "indices:data/read/search", normalUserWithoutSystemIndex); } // search all indices @@ -151,10 +151,10 @@ public void testDeleteAsAdmin() { for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executeDeleteRequest(index + "/_doc/document1", allAccessUserHeader); - validateForbiddenResponse(response, "", allAccessUser); + validateForbiddenResponse(response, "indices:data/write/delete", allAccessUser); response = restHelper.executeDeleteRequest(index, allAccessUserHeader); - validateForbiddenResponse(response, "", allAccessUser); + validateForbiddenResponse(response, "indices:admin/delete", allAccessUser); } } @@ -166,10 +166,10 @@ public void testDeleteAsNormalUser() { // permission for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executeDeleteRequest(index + "/_doc/document1", normalUserHeader); - shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", normalUser); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "indices:data/write/delete", normalUser); response = restHelper.executeDeleteRequest(index, normalUserHeader); - shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", normalUser); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "indices:admin/delete", normalUser); } } @@ -183,10 +183,10 @@ public void testDeleteAsNormalUserWithoutSystemIndexAccess() { index + "/_doc/document1", normalUserWithoutSystemIndexHeader ); - validateForbiddenResponse(response, "", normalUserWithoutSystemIndex); + validateForbiddenResponse(response, "indices:data/write/delete", normalUserWithoutSystemIndex); response = restHelper.executeDeleteRequest(index, normalUserWithoutSystemIndexHeader); - validateForbiddenResponse(response, "", normalUserWithoutSystemIndex); + validateForbiddenResponse(response, "indices:admin/delete", normalUserWithoutSystemIndex); } } @@ -217,11 +217,11 @@ public void testCloseOpenAsNormalUser() { for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_close", "", normalUserHeader); - shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", normalUser); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "indices:admin/close", normalUser); // normal user cannot open or close security index response = restHelper.executePostRequest(index + "/_open", "", normalUserHeader); - shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", normalUser); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "indices:admin/open", normalUser); } } @@ -235,11 +235,11 @@ private void testCloseOpenWithUser(String user, Header header) { for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executePostRequest(index + "/_close", "", header); - validateForbiddenResponse(response, "", user); + validateForbiddenResponse(response, "indices:admin/close", user); // admin or normal user (without system index permission) cannot open or close any system index response = restHelper.executePostRequest(index + "/_open", "", header); - validateForbiddenResponse(response, "", user); + validateForbiddenResponse(response, "indices:admin/open", user); } } @@ -314,10 +314,10 @@ public void testUpdateAsNormalUser() { for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executePutRequest(index + "/_settings", updateIndexSettings, normalUserHeader); - shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", normalUser); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "indices:admin/settings/update", normalUser); response = restHelper.executePutRequest(index + "/_mapping", newMappings, normalUserHeader); - shouldBeAllowedOnlyForAuthorizedIndices(index, response, "", normalUser); + shouldBeAllowedOnlyForAuthorizedIndices(index, response, "indices:admin/mapping/put", normalUser); } } @@ -331,10 +331,10 @@ private void testUpdateWithUser(String user, Header header) { for (String index : SYSTEM_INDICES) { RestHelper.HttpResponse response = restHelper.executePutRequest(index + "/_settings", updateIndexSettings, header); - validateForbiddenResponse(response, "", user); + validateForbiddenResponse(response, "indices:admin/settings/update", user); response = restHelper.executePutRequest(index + "/_mapping", newMappings, header); - validateForbiddenResponse(response, "", user); + validateForbiddenResponse(response, "indices:admin/mapping/put", user); } } @@ -393,14 +393,14 @@ public void testSnapshotSystemIndicesAsAdmin() { "", allAccessUserHeader ); - validateForbiddenResponse(res, "", allAccessUser); + validateForbiddenResponse(res, "cluster:admin/snapshot/restore", allAccessUser); res = restHelper.executePostRequest( "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", allAccessUserHeader ); - shouldBeAllowedOnlyForAuthorizedIndices(index, res, "", allAccessUser); + shouldBeAllowedOnlyForAuthorizedIndices(index, res, "cluster:admin/snapshot/restore", allAccessUser); } } @@ -424,7 +424,7 @@ public void testSnapshotSystemIndicesAsNormalUser() { "", normalUserHeader ); - shouldBeAllowedOnlyForAuthorizedIndices(index, res, "", normalUser); + shouldBeAllowedOnlyForAuthorizedIndices(index, res, "cluster:admin/snapshot/restore", normalUser); res = restHelper.executePostRequest( "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", @@ -432,7 +432,9 @@ public void testSnapshotSystemIndicesAsNormalUser() { normalUserHeader ); - String action = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) ? "" : "indices:data/write/index, indices:admin/create"; + String action = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) + ? "cluster:admin/snapshot/restore" + : "indices:data/write/index, indices:admin/create"; validateForbiddenResponse(res, action, normalUser); } } @@ -457,14 +459,16 @@ public void testSnapshotSystemIndicesAsNormalUserWithoutSystemIndexAccess() { "", normalUserWithoutSystemIndexHeader ); - validateForbiddenResponse(res, "", normalUserWithoutSystemIndex); + validateForbiddenResponse(res, "cluster:admin/snapshot/restore", normalUserWithoutSystemIndex); res = restHelper.executePostRequest( "_snapshot/" + index + "/" + index + "_1/_restore?wait_for_completion=true", "{ \"rename_pattern\": \"(.+)\", \"rename_replacement\": \"restored_index_with_global_state_$1\" }", normalUserWithoutSystemIndexHeader ); - String action = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) ? "" : "indices:data/write/index, indices:admin/create"; + String action = index.equals(ACCESSIBLE_ONLY_BY_SUPER_ADMIN) + ? "cluster:admin/snapshot/restore" + : "indices:data/write/index, indices:admin/create"; validateForbiddenResponse(res, action, normalUserWithoutSystemIndex); } }