From e65b2489a88581f6701d275b1dc4e29a8138adab Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Mon, 19 Aug 2024 17:12:55 -0400 Subject: [PATCH 01/49] Implement new extension points in IdentityPlugin and add ContextProvidingPluginSubject Signed-off-by: Craig Perkins --- .../opensearch/security/SystemIndexTests.java | 27 +++++- .../http/ExampleSystemIndexPlugin.java | 27 ------ .../IndexDocumentIntoSystemIndexAction.java | 12 +++ .../IndexDocumentIntoSystemIndexRequest.java | 30 +++++++ .../IndexDocumentIntoSystemIndexResponse.java | 36 ++++++++ .../security/plugin/PluginSubjectHolder.java | 26 ++++++ ...estIndexDocumentIntoSystemIndexAction.java | 38 ++++++++ .../security/plugin/SystemIndexPlugin1.java | 88 +++++++++++++++++++ ...ortIndexDocumentIntoSystemIndexAction.java | 62 +++++++++++++ .../security/OpenSearchSecurityPlugin.java | 19 +++- .../ContextProvidingPluginSubject.java | 36 ++++++++ .../opensearch/security/user/UserService.java | 10 +++ 12 files changed, 381 insertions(+), 30 deletions(-) delete mode 100644 src/integrationTest/java/org/opensearch/security/http/ExampleSystemIndexPlugin.java create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexAction.java create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/PluginSubjectHolder.java create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java create mode 100644 src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 599ffe9ad2..d841725843 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.common.util.FeatureFlags; import org.opensearch.core.rest.RestStatus; -import org.opensearch.security.http.ExampleSystemIndexPlugin; +import org.opensearch.security.plugin.SystemIndexPlugin1; import org.opensearch.test.framework.TestSecurityConfig.AuthcDomain; import org.opensearch.test.framework.cluster.ClusterManager; import org.opensearch.test.framework.cluster.LocalCluster; @@ -26,7 +28,9 @@ 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.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; @@ -43,9 +47,11 @@ public class SystemIndexTests { .anonymousAuth(false) .authc(AUTHC_DOMAIN) .users(USER_ADMIN) - .plugin(ExampleSystemIndexPlugin.class) + .plugin(SystemIndexPlugin1.class) .nodeSettings( Map.of( + FeatureFlags.IDENTITY, + true, SECURITY_RESTAPI_ROLES_ENABLED, List.of("user_" + USER_ADMIN.getName() + "__" + ALL_ACCESS.getName()), SECURITY_SYSTEM_INDICES_ENABLED_KEY, @@ -54,6 +60,13 @@ public class SystemIndexTests { ) .build(); + @Before + public void wipeAllIndices() { + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + HttpResponse response = client.delete(".system-index1"); + } + } + @Test public void adminShouldNotBeAbleToDeleteSecurityIndex() { try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { @@ -80,4 +93,14 @@ public void adminShouldNotBeAbleToDeleteSecurityIndex() { assertThat(response4.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); } } + + @Test + public void testPluginShouldBeAbleToIndexDocumentIntoItsSystemIndex() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.put("_plugins/system-index/" + SYSTEM_INDEX_1); + + assertThat(response.getStatusCode(), equalTo(RestStatus.OK.getStatus())); + assertThat(response.getBody(), containsString(SystemIndexPlugin1.class.getCanonicalName())); + } + } } 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..40d3dd533d --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexAction.java @@ -0,0 +1,12 @@ +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..f61f26b713 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java @@ -0,0 +1,30 @@ +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; + + public IndexDocumentIntoSystemIndexRequest(String indexName) { + this.indexName = indexName; + } + + public IndexDocumentIntoSystemIndexRequest(StreamInput in) throws IOException { + super(in); + this.indexName = in.readString(); + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + public String getIndexName() { + return this.indexName; + } +} 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..dddd260376 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java @@ -0,0 +1,36 @@ +package org.opensearch.security.plugin; + +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; + +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/PluginSubjectHolder.java b/src/integrationTest/java/org/opensearch/security/plugin/PluginSubjectHolder.java new file mode 100644 index 0000000000..7eb7966727 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/PluginSubjectHolder.java @@ -0,0 +1,26 @@ +package org.opensearch.security.plugin; + +import org.opensearch.identity.PluginSubject; + +public class PluginSubjectHolder { + private static final PluginSubjectHolder INSTANCE = new PluginSubjectHolder(); + + private PluginSubject pluginSystemSubject; + + private PluginSubjectHolder() {} + + public void initialize(PluginSubject pluginSystemSubject) { + // if (pluginSystemSubject != null) { + // throw new OpenSearchSecurityException("pluginSystemSubjectHolder can only be initialized once"); + // } + this.pluginSystemSubject = pluginSystemSubject; + } + + public static PluginSubjectHolder getInstance() { + return PluginSubjectHolder.INSTANCE; + } + + public PluginSubject getPluginSystemSubject() { + return pluginSystemSubject; + } +} 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..38fa943ce1 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java @@ -0,0 +1,38 @@ +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, "/_plugins/system-index/{index}")); + } + + @Override + public String getName() { + return "test_index_document_into_system_index_action"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + String indexName = request.param("index"); + IndexDocumentIntoSystemIndexRequest indexRequest = new IndexDocumentIntoSystemIndexRequest(indexName); + return channel -> client.execute(IndexDocumentIntoSystemIndexAction.INSTANCE, indexRequest, new RestToXContentListener<>(channel)); + } +} 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..b5182f3c87 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -0,0 +1,88 @@ +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.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 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 List.of(); + } + + @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)); + } + + @Override + public List> getActions() { + return Arrays.asList( + new ActionHandler<>(IndexDocumentIntoSystemIndexAction.INSTANCE, TransportIndexDocumentIntoSystemIndexAction.class) + ); + } + + @Override + public void assignSubject(PluginSubject pluginSystemSubject) { + PluginSubjectHolder.getInstance().initialize(pluginSystemSubject); + } +} 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..9118ecee11 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java @@ -0,0 +1,62 @@ +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.PluginSubject; +import org.opensearch.security.identity.ContextProvidingPluginSubject; +import org.opensearch.tasks.Task; +import org.opensearch.transport.TransportService; + +public class TransportIndexDocumentIntoSystemIndexAction extends HandledTransportAction< + IndexDocumentIntoSystemIndexRequest, + IndexDocumentIntoSystemIndexResponse> { + + private final Client client; + private final PluginSubject pluginSystemSubject; + + @Inject + public TransportIndexDocumentIntoSystemIndexAction( + final TransportService transportService, + final ActionFilters actionFilters, + final Client client + ) { + super(IndexDocumentIntoSystemIndexAction.NAME, transportService, actionFilters, IndexDocumentIntoSystemIndexRequest::new); + this.client = client; + this.pluginSystemSubject = PluginSubjectHolder.getInstance().getPluginSystemSubject(); + } + + @Override + protected void doExecute( + Task task, + IndexDocumentIntoSystemIndexRequest request, + ActionListener actionListener + ) { + String indexName = request.getIndexName(); + try { + pluginSystemSubject.runAs(() -> { + client.admin().indices().create(new CreateIndexRequest(indexName), ActionListener.wrap(r -> { + client.index( + new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON), + ActionListener.wrap(r2 -> { + String subjectHeader = client.threadPool() + .getThreadContext() + .getHeader(ContextProvidingPluginSubject.SUBJECT_HEADER); + actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, subjectHeader)); + }, actionListener::onFailure) + ); + }, actionListener::onFailure)); + return null; + }); + } catch (Exception ex) { + throw new RuntimeException("Unexpected error: " + ex.getMessage()); + } + } +} diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 509b98f12e..5f83c16a24 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -109,6 +109,7 @@ import org.opensearch.http.HttpServerTransport; import org.opensearch.http.HttpServerTransport.Dispatcher; 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; @@ -119,6 +120,7 @@ import org.opensearch.plugins.ExtensionAwarePlugin; import org.opensearch.plugins.IdentityPlugin; import org.opensearch.plugins.MapperPlugin; +import org.opensearch.plugins.Plugin; import org.opensearch.plugins.SecureHttpTransportSettingsProvider; import org.opensearch.plugins.SecureSettingsFactory; import org.opensearch.plugins.SecureTransportSettingsProvider; @@ -164,6 +166,7 @@ import org.opensearch.security.hasher.PasswordHasherFactory; import org.opensearch.security.http.NonSslHttpServerTransport; import org.opensearch.security.http.XFFResolver; +import org.opensearch.security.identity.ContextProvidingPluginSubject; import org.opensearch.security.identity.SecurityTokenManager; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.privileges.PrivilegesInterceptor; @@ -2102,7 +2105,7 @@ private static String handleKeyword(final String field) { } @Override - public Subject getSubject() { + public Subject getCurrentSubject() { // Not supported return new NoopSubject(); } @@ -2112,6 +2115,20 @@ public SecurityTokenManager getTokenManager() { return tokenManager; } + @Override + public PluginSubject getPluginSubject(Plugin plugin) { + return new ContextProvidingPluginSubject(threadPool, plugin); + } + + @Override + public UnaryOperator authenticate(ThreadContext threadContext) { + if (client || disabled || SSLConfig.isSslOnlyMode()) { + return (rh) -> rh; + } + + return (rh) -> securityRestHandler.wrap(rh, adminDns); + } + @Override public Optional getSecureSettingFactory(Settings settings) { return Optional.of(new OpenSearchSecureSettingsFactory(threadPool, sks, sslExceptionHandler, securityRestHandler)); 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..d706325835 --- /dev/null +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java @@ -0,0 +1,36 @@ +package org.opensearch.security.identity; + +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.PluginSubject; +import org.opensearch.plugins.Plugin; +import org.opensearch.threadpool.ThreadPool; + +public class ContextProvidingPluginSubject implements PluginSubject { + public static final String SUBJECT_HEADER = "_security_subject"; + + private final ThreadPool threadPool; + private final String pluginCanonicalClassName; + + public ContextProvidingPluginSubject(ThreadPool threadPool, Plugin plugin) { + super(); + this.threadPool = threadPool; + this.pluginCanonicalClassName = plugin.getClass().getCanonicalName(); + } + + @Override + public Principal getPrincipal() { + return NamedPrincipal.UNAUTHENTICATED; + } + + @Override + public T runAs(Callable callable) throws Exception { + try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) { + threadPool.getThreadContext().putHeader(SUBJECT_HEADER, pluginCanonicalClassName); + return callable.call(); + } + } +} diff --git a/src/main/java/org/opensearch/security/user/UserService.java b/src/main/java/org/opensearch/security/user/UserService.java index f380fdb328..2302bb818f 100644 --- a/src/main/java/org/opensearch/security/user/UserService.java +++ b/src/main/java/org/opensearch/security/user/UserService.java @@ -41,6 +41,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.common.xcontent.XContentType; +import org.opensearch.identity.PluginSubject; import org.opensearch.identity.tokens.AuthToken; import org.opensearch.identity.tokens.BasicAuthToken; import org.opensearch.security.DefaultObjectMapper; @@ -69,6 +70,7 @@ public class UserService { private final PasswordHasher passwordHasher; String securityIndex; Client client; + PluginSubject pluginSystemSubject; User tokenUser; final static String NO_PASSWORD_OR_HASH_MESSAGE = "Please specify either 'hash' or 'password' when creating a new internal user."; @@ -111,6 +113,14 @@ public UserService( this.client = client; } + void setPluginSystemSubject(PluginSubject pluginSystemSubject) { + this.pluginSystemSubject = pluginSystemSubject; + } + + public PluginSubject getPluginSystemSubject() { + return pluginSystemSubject; + } + /** * Load data for a given CType * @param config CType whose data is to be loaded in-memory From 55a7e45a78dce8901e6fd5f1e7ab8f4d37c5c522 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Aug 2024 09:01:51 -0400 Subject: [PATCH 02/49] Remove code from UserService Signed-off-by: Craig Perkins --- .../java/org/opensearch/security/user/UserService.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/org/opensearch/security/user/UserService.java b/src/main/java/org/opensearch/security/user/UserService.java index 2302bb818f..192bfc4005 100644 --- a/src/main/java/org/opensearch/security/user/UserService.java +++ b/src/main/java/org/opensearch/security/user/UserService.java @@ -113,14 +113,6 @@ public UserService( this.client = client; } - void setPluginSystemSubject(PluginSubject pluginSystemSubject) { - this.pluginSystemSubject = pluginSystemSubject; - } - - public PluginSubject getPluginSystemSubject() { - return pluginSystemSubject; - } - /** * Load data for a given CType * @param config CType whose data is to be loaded in-memory From 44c0d76fc5099e53c199c62185eba3264b669cdb Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Aug 2024 10:49:37 -0400 Subject: [PATCH 03/49] Remove commented out code Signed-off-by: Craig Perkins --- .../opensearch/security/plugin/PluginSubjectHolder.java | 3 --- .../opensearch/security/OpenSearchSecurityPlugin.java | 9 --------- .../java/org/opensearch/security/user/UserService.java | 2 -- 3 files changed, 14 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/plugin/PluginSubjectHolder.java b/src/integrationTest/java/org/opensearch/security/plugin/PluginSubjectHolder.java index 7eb7966727..086efcc16c 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/PluginSubjectHolder.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/PluginSubjectHolder.java @@ -10,9 +10,6 @@ public class PluginSubjectHolder { private PluginSubjectHolder() {} public void initialize(PluginSubject pluginSystemSubject) { - // if (pluginSystemSubject != null) { - // throw new OpenSearchSecurityException("pluginSystemSubjectHolder can only be initialized once"); - // } this.pluginSystemSubject = pluginSystemSubject; } diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 5f83c16a24..d5eddb0af6 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -2120,15 +2120,6 @@ public PluginSubject getPluginSubject(Plugin plugin) { return new ContextProvidingPluginSubject(threadPool, plugin); } - @Override - public UnaryOperator authenticate(ThreadContext threadContext) { - if (client || disabled || SSLConfig.isSslOnlyMode()) { - return (rh) -> rh; - } - - return (rh) -> securityRestHandler.wrap(rh, adminDns); - } - @Override public Optional getSecureSettingFactory(Settings settings) { return Optional.of(new OpenSearchSecureSettingsFactory(threadPool, sks, sslExceptionHandler, securityRestHandler)); diff --git a/src/main/java/org/opensearch/security/user/UserService.java b/src/main/java/org/opensearch/security/user/UserService.java index 192bfc4005..f380fdb328 100644 --- a/src/main/java/org/opensearch/security/user/UserService.java +++ b/src/main/java/org/opensearch/security/user/UserService.java @@ -41,7 +41,6 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.xcontent.XContentHelper; import org.opensearch.common.xcontent.XContentType; -import org.opensearch.identity.PluginSubject; import org.opensearch.identity.tokens.AuthToken; import org.opensearch.identity.tokens.BasicAuthToken; import org.opensearch.security.DefaultObjectMapper; @@ -70,7 +69,6 @@ public class UserService { private final PasswordHasher passwordHasher; String securityIndex; Client client; - PluginSubject pluginSystemSubject; User tokenUser; final static String NO_PASSWORD_OR_HASH_MESSAGE = "Please specify either 'hash' or 'password' when creating a new internal user."; From 3e17b576d5733da130e64abbf26cae3ae450e128 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Aug 2024 11:22:58 -0400 Subject: [PATCH 04/49] Use guice dependencies Signed-off-by: Craig Perkins --- .../security/plugin/PluginSubjectHolder.java | 23 ------------------- .../security/plugin/SystemIndexPlugin1.java | 7 ++++-- .../plugin/TransportActionDependencies.java | 17 ++++++++++++++ ...ortIndexDocumentIntoSystemIndexAction.java | 5 ++-- 4 files changed, 25 insertions(+), 27 deletions(-) delete mode 100644 src/integrationTest/java/org/opensearch/security/plugin/PluginSubjectHolder.java create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/TransportActionDependencies.java diff --git a/src/integrationTest/java/org/opensearch/security/plugin/PluginSubjectHolder.java b/src/integrationTest/java/org/opensearch/security/plugin/PluginSubjectHolder.java deleted file mode 100644 index 086efcc16c..0000000000 --- a/src/integrationTest/java/org/opensearch/security/plugin/PluginSubjectHolder.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.opensearch.security.plugin; - -import org.opensearch.identity.PluginSubject; - -public class PluginSubjectHolder { - private static final PluginSubjectHolder INSTANCE = new PluginSubjectHolder(); - - private PluginSubject pluginSystemSubject; - - private PluginSubjectHolder() {} - - public void initialize(PluginSubject pluginSystemSubject) { - this.pluginSystemSubject = pluginSystemSubject; - } - - public static PluginSubjectHolder getInstance() { - return PluginSubjectHolder.INSTANCE; - } - - public PluginSubject getPluginSystemSubject() { - return pluginSystemSubject; - } -} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java index b5182f3c87..7b2e52fcee 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -35,6 +35,8 @@ public class SystemIndexPlugin1 extends Plugin implements SystemIndexPlugin, IdentityAwarePlugin { public static final String SYSTEM_INDEX_1 = ".system-index1"; + private static final TransportActionDependencies SECURITY_TRANSPORT_ACTION_DEPENDENCIES = new TransportActionDependencies(); + private Client client; @Override @@ -52,7 +54,7 @@ public Collection createComponents( Supplier repositoriesServiceSupplier ) { this.client = client; - return List.of(); + return List.of(SECURITY_TRANSPORT_ACTION_DEPENDENCIES); } @Override @@ -71,6 +73,7 @@ public List getRestHandlers( IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster ) { + TransportActionDependencies deps = new TransportActionDependencies(); return List.of(new RestIndexDocumentIntoSystemIndexAction(client)); } @@ -83,6 +86,6 @@ public List getRestHandlers( @Override public void assignSubject(PluginSubject pluginSystemSubject) { - PluginSubjectHolder.getInstance().initialize(pluginSystemSubject); + SECURITY_TRANSPORT_ACTION_DEPENDENCIES.setPluginSystemSubject(pluginSystemSubject); } } diff --git a/src/integrationTest/java/org/opensearch/security/plugin/TransportActionDependencies.java b/src/integrationTest/java/org/opensearch/security/plugin/TransportActionDependencies.java new file mode 100644 index 0000000000..0d6dd8d23f --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportActionDependencies.java @@ -0,0 +1,17 @@ +package org.opensearch.security.plugin; + +import org.opensearch.identity.PluginSubject; + +public class TransportActionDependencies { + private PluginSubject pluginSystemSubject; + + public TransportActionDependencies() {} + + public void setPluginSystemSubject(PluginSubject pluginSystemSubject) { + this.pluginSystemSubject = pluginSystemSubject; + } + + public PluginSubject getPluginSystemSubject() { + return pluginSystemSubject; + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java index 9118ecee11..0fa26698ae 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java @@ -25,11 +25,12 @@ public class TransportIndexDocumentIntoSystemIndexAction extends HandledTranspor public TransportIndexDocumentIntoSystemIndexAction( final TransportService transportService, final ActionFilters actionFilters, - final Client client + final Client client, + final TransportActionDependencies deps ) { super(IndexDocumentIntoSystemIndexAction.NAME, transportService, actionFilters, IndexDocumentIntoSystemIndexRequest::new); this.client = client; - this.pluginSystemSubject = PluginSubjectHolder.getInstance().getPluginSystemSubject(); + this.pluginSystemSubject = deps.getPluginSystemSubject(); } @Override From 0add3d8c7ae433c32243d2c8b4a45a25c62c8ffa Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Aug 2024 11:56:40 -0400 Subject: [PATCH 05/49] Move TransportActionDependencies Signed-off-by: Craig Perkins --- .../java/org/opensearch/security/plugin/SystemIndexPlugin1.java | 1 + .../plugin/TransportIndexDocumentIntoSystemIndexAction.java | 1 + .../security/identity}/TransportActionDependencies.java | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) rename src/{integrationTest/java/org/opensearch/security/plugin => main/java/org/opensearch/security/identity}/TransportActionDependencies.java (90%) diff --git a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java index 7b2e52fcee..bbfeb5fb3c 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -29,6 +29,7 @@ import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; import org.opensearch.script.ScriptService; +import org.opensearch.security.identity.TransportActionDependencies; import org.opensearch.threadpool.ThreadPool; import org.opensearch.watcher.ResourceWatcherService; diff --git a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java index 0fa26698ae..1a77f1aaff 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java @@ -11,6 +11,7 @@ import org.opensearch.core.action.ActionListener; import org.opensearch.identity.PluginSubject; import org.opensearch.security.identity.ContextProvidingPluginSubject; +import org.opensearch.security.identity.TransportActionDependencies; import org.opensearch.tasks.Task; import org.opensearch.transport.TransportService; diff --git a/src/integrationTest/java/org/opensearch/security/plugin/TransportActionDependencies.java b/src/main/java/org/opensearch/security/identity/TransportActionDependencies.java similarity index 90% rename from src/integrationTest/java/org/opensearch/security/plugin/TransportActionDependencies.java rename to src/main/java/org/opensearch/security/identity/TransportActionDependencies.java index 0d6dd8d23f..4a778641c6 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/TransportActionDependencies.java +++ b/src/main/java/org/opensearch/security/identity/TransportActionDependencies.java @@ -1,4 +1,4 @@ -package org.opensearch.security.plugin; +package org.opensearch.security.identity; import org.opensearch.identity.PluginSubject; From b76a5b8f0e7d1039a6fa762c821b9992b563bd22 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Aug 2024 11:59:06 -0400 Subject: [PATCH 06/49] Use NamedPrincipal Signed-off-by: Craig Perkins --- .../security/identity/ContextProvidingPluginSubject.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java index d706325835..98b2480840 100644 --- a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java @@ -13,23 +13,23 @@ public class ContextProvidingPluginSubject implements PluginSubject { public static final String SUBJECT_HEADER = "_security_subject"; private final ThreadPool threadPool; - private final String pluginCanonicalClassName; + private final NamedPrincipal pluginPrincipal; public ContextProvidingPluginSubject(ThreadPool threadPool, Plugin plugin) { super(); this.threadPool = threadPool; - this.pluginCanonicalClassName = plugin.getClass().getCanonicalName(); + this.pluginPrincipal = new NamedPrincipal(plugin.getClass().getCanonicalName()); } @Override public Principal getPrincipal() { - return NamedPrincipal.UNAUTHENTICATED; + return pluginPrincipal; } @Override public T runAs(Callable callable) throws Exception { try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) { - threadPool.getThreadContext().putHeader(SUBJECT_HEADER, pluginCanonicalClassName); + threadPool.getThreadContext().putHeader(SUBJECT_HEADER, pluginPrincipal.getName()); return callable.call(); } } From 2da6f2a801251e6e6651efd4d60dca30fb8a4927 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Aug 2024 14:58:59 -0400 Subject: [PATCH 07/49] Add end to end test to show stronger index protection for plugins Signed-off-by: Craig Perkins --- .../java/org/opensearch/security/plugin/SystemIndexPlugin2.java | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin2.java 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..f65153bef6 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin2.java @@ -0,0 +1,2 @@ +package org.opensearch.security.plugin;public class SystemIndexPlugin2 { +} From 9cf80aeb7b4965590901f57990e0670cb3f63056 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Aug 2024 14:59:06 -0400 Subject: [PATCH 08/49] Add end to end test to show stronger index protection for plugins Signed-off-by: Craig Perkins --- .../opensearch/security/SystemIndexTests.java | 37 ++++++----- .../IndexDocumentIntoSystemIndexAction.java | 10 +++ .../IndexDocumentIntoSystemIndexRequest.java | 10 +++ .../IndexDocumentIntoSystemIndexResponse.java | 10 +++ ...estIndexDocumentIntoSystemIndexAction.java | 10 +++ .../security/plugin/SystemIndexPlugin1.java | 10 +++ .../security/plugin/SystemIndexPlugin2.java | 61 ++++++++++++++++++- ...ortIndexDocumentIntoSystemIndexAction.java | 23 +++++-- .../test/framework/cluster/LocalCluster.java | 11 +++- .../security/filter/SecurityFilter.java | 2 + .../ContextProvidingPluginSubject.java | 8 ++- .../privileges/PrivilegesEvaluator.java | 4 +- .../SystemIndexAccessEvaluator.java | 19 +++++- .../org/opensearch/security/user/User.java | 12 +++- 14 files changed, 199 insertions(+), 28 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index d841725843..585642f42c 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -13,7 +13,6 @@ 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; @@ -21,6 +20,7 @@ import org.opensearch.common.util.FeatureFlags; import org.opensearch.core.rest.RestStatus; 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; @@ -31,8 +31,10 @@ 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.security.support.ConfigConstants.SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY; import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; import static org.opensearch.test.framework.TestSecurityConfig.User.USER_ADMIN; @@ -47,11 +49,13 @@ public class SystemIndexTests { .anonymousAuth(false) .authc(AUTHC_DOMAIN) .users(USER_ADMIN) - .plugin(SystemIndexPlugin1.class) + .plugin(List.of(SystemIndexPlugin1.class, SystemIndexPlugin2.class)) .nodeSettings( Map.of( FeatureFlags.IDENTITY, true, + SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY, + true, SECURITY_RESTAPI_ROLES_ENABLED, List.of("user_" + USER_ADMIN.getName() + "__" + ALL_ACCESS.getName()), SECURITY_SYSTEM_INDICES_ENABLED_KEY, @@ -60,13 +64,6 @@ public class SystemIndexTests { ) .build(); - @Before - public void wipeAllIndices() { - try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { - HttpResponse response = client.delete(".system-index1"); - } - } - @Test public void adminShouldNotBeAbleToDeleteSecurityIndex() { try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { @@ -82,15 +79,10 @@ public void adminShouldNotBeAbleToDeleteSecurityIndex() { assertThat(response2.getStatusCode(), equalTo(RestStatus.OK.getStatus())); - // regular use can create system index + // regular use cannot create system index when system index protection is enforced HttpResponse response3 = client.put(".system-index1"); - assertThat(response3.getStatusCode(), equalTo(RestStatus.OK.getStatus())); - - // regular user cannot delete system index - HttpResponse response4 = client.delete(".system-index1"); - - assertThat(response4.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); + assertThat(response3.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); } } @@ -103,4 +95,17 @@ public void testPluginShouldBeAbleToIndexDocumentIntoItsSystemIndex() { assertThat(response.getBody(), containsString(SystemIndexPlugin1.class.getCanonicalName())); } } + + @Test + public void testPluginShouldNotBeAbleToIndexDocumentIntoSystemIndexRegisteredByOtherPlugin() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.put("_plugins/system-index/" + SYSTEM_INDEX_2); + + assertThat(response.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); + assertThat( + response.getBody(), + containsString("no permissions for [indices:admin/create] and User [name=org.opensearch.security.plugin.SystemIndexPlugin1") + ); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexAction.java index 40d3dd533d..8621f2a609 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexAction.java @@ -1,3 +1,13 @@ +/* + * 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; diff --git a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java index f61f26b713..5cf7cf90d9 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java @@ -1,3 +1,13 @@ +/* + * 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; diff --git a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java index dddd260376..cc72c64887 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java @@ -1,3 +1,13 @@ +/* + * 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; diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java index 38fa943ce1..614d020ced 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java @@ -1,3 +1,13 @@ +/* + * 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; diff --git a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java index bbfeb5fb3c..bd0d7cec70 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -1,3 +1,13 @@ +/* + * 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; diff --git a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin2.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin2.java index f65153bef6..8fcf23e3db 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin2.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin2.java @@ -1,2 +1,61 @@ -package org.opensearch.security.plugin;public class SystemIndexPlugin2 { +/* + * 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 index 1a77f1aaff..39cbfa3656 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java @@ -1,3 +1,13 @@ +/* + * 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; @@ -10,9 +20,11 @@ import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.action.ActionListener; import org.opensearch.identity.PluginSubject; -import org.opensearch.security.identity.ContextProvidingPluginSubject; import org.opensearch.security.identity.TransportActionDependencies; +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< @@ -20,6 +32,7 @@ public class TransportIndexDocumentIntoSystemIndexAction extends HandledTranspor IndexDocumentIntoSystemIndexResponse> { private final Client client; + private final ThreadPool threadPool; private final PluginSubject pluginSystemSubject; @Inject @@ -27,10 +40,12 @@ public TransportIndexDocumentIntoSystemIndexAction( final TransportService transportService, final ActionFilters actionFilters, final Client client, + final ThreadPool threadPool, final TransportActionDependencies deps ) { super(IndexDocumentIntoSystemIndexAction.NAME, transportService, actionFilters, IndexDocumentIntoSystemIndexRequest::new); this.client = client; + this.threadPool = threadPool; this.pluginSystemSubject = deps.getPluginSystemSubject(); } @@ -48,10 +63,8 @@ protected void doExecute( new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) .source("{\"content\":1}", XContentType.JSON), ActionListener.wrap(r2 -> { - String subjectHeader = client.threadPool() - .getThreadContext() - .getHeader(ContextProvidingPluginSubject.SUBJECT_HEADER); - actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, subjectHeader)); + User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, user.getName())); }, actionListener::onFailure) ); }, actionListener::onFailure)); 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 894bb5baa9..9d0b084655 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -379,7 +379,7 @@ public Builder nodeSettings(Map settings) { } /** - * Adds additional plugins to the cluster + * Adds additional plugin to the cluster */ public Builder plugin(Class plugin) { this.plugins.add(plugin); @@ -387,6 +387,15 @@ public Builder plugin(Class plugin) { return this; } + /** + * Adds additional plugins to the cluster + */ + public Builder plugin(List> plugins) { + this.plugins.addAll(plugins); + + return this; + } + public Builder authFailureListeners(AuthFailureListeners listener) { testSecurityConfig.authFailureListeners(listener); return this; diff --git a/src/main/java/org/opensearch/security/filter/SecurityFilter.java b/src/main/java/org/opensearch/security/filter/SecurityFilter.java index 1116e70845..96b015c830 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityFilter.java @@ -202,6 +202,8 @@ private void ap // However, if another plugin injected a user in the ThreadContext, we still need // to perform privileges checks. enforcePrivilegesEvaluation = true; + } else if (user != null && user.isPluginUser()) { + enforcePrivilegesEvaluation = true; } final boolean userIsAdmin = isUserAdmin(user, adminDns); final boolean interClusterRequest = HeaderHelper.isInterClusterRequest(threadContext); diff --git a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java index 98b2480840..2517349606 100644 --- a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java @@ -1,12 +1,15 @@ package org.opensearch.security.identity; import java.security.Principal; +import java.util.Map; import java.util.concurrent.Callable; 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 { @@ -14,11 +17,13 @@ public class ContextProvidingPluginSubject implements PluginSubject { private final ThreadPool threadPool; private final NamedPrincipal pluginPrincipal; + private final User pluginUser; public ContextProvidingPluginSubject(ThreadPool threadPool, Plugin plugin) { super(); this.threadPool = threadPool; this.pluginPrincipal = new NamedPrincipal(plugin.getClass().getCanonicalName()); + this.pluginUser = new User(pluginPrincipal.getName()); } @Override @@ -29,7 +34,8 @@ public Principal getPrincipal() { @Override public T runAs(Callable callable) throws Exception { try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) { - threadPool.getThreadContext().putHeader(SUBJECT_HEADER, pluginPrincipal.getName()); + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, pluginUser); + pluginUser.addAttributes(Map.of("attr.internal.plugin", "true")); return callable.call(); } } diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 199442ee03..c6ea139b3e 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -292,7 +292,7 @@ public PrivilegesEvaluatorResponse evaluate(PrivilegesEvaluationContext context) log.debug("Mapped roles: {}", mappedRoles.toString()); } - if (request instanceof BulkRequest && (Strings.isNullOrEmpty(user.getRequestedTenant()))) { + if (request instanceof BulkRequest && (Strings.isNullOrEmpty(user.getRequestedTenant())) && !user.isPluginUser()) { // Shortcut for bulk actions. The details are checked on the lower level of the BulkShardRequests (Action // indices:data/write/bulk[s]). // This shortcut is only possible if the default tenant is selected, as we might need to rewrite the request for non-default @@ -327,7 +327,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/SystemIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java index 38825a9bf1..f2ed737959 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -255,9 +255,10 @@ private void evaluateSystemIndicesAccess( boolean containsSystemIndex = requestContainsAnySystemIndices(requestedResolved); boolean containsRegularIndex = requestContainsAnyRegularIndices(requestedResolved); boolean serviceAccountUser = user.isServiceAccount(); + boolean pluginUser = user.isPluginUser(); if (isSystemIndexPermissionEnabled) { - if (serviceAccountUser && containsRegularIndex) { + if ((serviceAccountUser || pluginUser) && containsRegularIndex) { auditLog.logSecurityIndexAttempt(request, action, task); if (!containsSystemIndex && log.isInfoEnabled()) { log.info("{} not permitted for a service account {} on non-system indices.", action, securityRoles); @@ -289,6 +290,22 @@ private void evaluateSystemIndicesAccess( presponse.allowed = false; presponse.markComplete(); return; + } else if (containsSystemIndex && pluginUser) { + boolean requestContainsOnlyPluginsRegisteredSystemIndices = requestedResolved.getAllIndices() + .equals(SystemIndexRegistry.matchesPluginSystemIndexPattern(user.getName(), requestedResolved.getAllIndices())); + if (requestContainsOnlyPluginsRegisteredSystemIndices) { + // allow all action types, but ensure that request only contains registered system indices for this plugin + presponse.allowed = true; + } else { + auditLog.logSecurityIndexAttempt(request, action, task); + if (log.isInfoEnabled()) { + log.info("{} can only interact with its own system indices", user.getName()); + } + presponse.missingPrivileges.add(action); + presponse.allowed = false; + } + presponse.markComplete(); + return; } else if (containsSystemIndex && !securityRoles.hasExplicitIndexPermission( requestedResolved, diff --git a/src/main/java/org/opensearch/security/user/User.java b/src/main/java/org/opensearch/security/user/User.java index 6abba3d734..fb3ef80853 100644 --- a/src/main/java/org/opensearch/security/user/User.java +++ b/src/main/java/org/opensearch/security/user/User.java @@ -290,10 +290,20 @@ 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 attribute, otherwise false + */ + public boolean isPluginUser() { + Map userAttributesMap = this.getCustomAttributesMap(); + return userAttributesMap != null && "true".equals(userAttributesMap.get("attr.internal.plugin")); + } } From 21fc225157e43432590408541588a270017c6dcf Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Aug 2024 15:02:38 -0400 Subject: [PATCH 09/49] Move to constructor Signed-off-by: Craig Perkins --- .../security/identity/ContextProvidingPluginSubject.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java index 2517349606..0df842b707 100644 --- a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java @@ -24,6 +24,7 @@ public ContextProvidingPluginSubject(ThreadPool threadPool, Plugin plugin) { this.threadPool = threadPool; this.pluginPrincipal = new NamedPrincipal(plugin.getClass().getCanonicalName()); this.pluginUser = new User(pluginPrincipal.getName()); + this.pluginUser.addAttributes(Map.of("attr.internal.plugin", "true")); } @Override @@ -35,7 +36,6 @@ public Principal getPrincipal() { public T runAs(Callable callable) throws Exception { try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) { threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, pluginUser); - pluginUser.addAttributes(Map.of("attr.internal.plugin", "true")); return callable.call(); } } From f08f311fda80967f737564c6fe78f103d4e43514 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Aug 2024 15:31:25 -0400 Subject: [PATCH 10/49] Remove unused constant Signed-off-by: Craig Perkins --- .../security/identity/ContextProvidingPluginSubject.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java index 0df842b707..041a941e01 100644 --- a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java @@ -13,8 +13,6 @@ import org.opensearch.threadpool.ThreadPool; public class ContextProvidingPluginSubject implements PluginSubject { - public static final String SUBJECT_HEADER = "_security_subject"; - private final ThreadPool threadPool; private final NamedPrincipal pluginPrincipal; private final User pluginUser; From 2de9a5ffa24c4cc955d14a83555dc965c93c2e89 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Aug 2024 16:07:18 -0400 Subject: [PATCH 11/49] Add test that demonstrates cluster action forbidden when running with plugin user Signed-off-by: Craig Perkins --- .../opensearch/security/SystemIndexTests.java | 19 ++++- ...estIndexDocumentIntoSystemIndexAction.java | 2 +- .../plugin/RestRunClusterHealthAction.java | 69 +++++++++++++++++++ .../security/plugin/SystemIndexPlugin1.java | 6 +- .../SystemIndexAccessEvaluator.java | 1 + 5 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 585642f42c..73063a1e2f 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -89,7 +89,7 @@ public void adminShouldNotBeAbleToDeleteSecurityIndex() { @Test public void testPluginShouldBeAbleToIndexDocumentIntoItsSystemIndex() { try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { - HttpResponse response = client.put("_plugins/system-index/" + SYSTEM_INDEX_1); + 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())); @@ -99,7 +99,7 @@ public void testPluginShouldBeAbleToIndexDocumentIntoItsSystemIndex() { @Test public void testPluginShouldNotBeAbleToIndexDocumentIntoSystemIndexRegisteredByOtherPlugin() { try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { - HttpResponse response = client.put("_plugins/system-index/" + SYSTEM_INDEX_2); + HttpResponse response = client.put("try-create-and-index/" + SYSTEM_INDEX_2); assertThat(response.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); assertThat( @@ -108,4 +108,19 @@ public void testPluginShouldNotBeAbleToIndexDocumentIntoSystemIndexRegisteredByO ); } } + + @Test + public void testPluginShouldNotBeAbleToRunClusterActions() { + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + HttpResponse response = client.get("try-cluster-health/"); + + assertThat(response.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); + assertThat( + response.getBody(), + containsString( + "no permissions for [cluster:monitor/health] and User [name=org.opensearch.security.plugin.SystemIndexPlugin1" + ) + ); + } + } } diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java index 614d020ced..a4a9fb3c6d 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java @@ -31,7 +31,7 @@ public RestIndexDocumentIntoSystemIndexAction(Client client) { @Override public List routes() { - return singletonList(new Route(PUT, "/_plugins/system-index/{index}")); + return singletonList(new Route(PUT, "/try-create-and-index/{index}")); } @Override 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..eef4cd99b9 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java @@ -0,0 +1,69 @@ +/* + * 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.cluster.health.ClusterHealthRequest; +import org.opensearch.action.admin.cluster.health.ClusterHealthResponse; +import org.opensearch.client.Client; +import org.opensearch.client.node.NodeClient; +import org.opensearch.core.action.ActionListener; +import org.opensearch.core.rest.RestStatus; +import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.identity.PluginSubject; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestRequest; + +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 PluginSubject pluginSubject; + + public RestRunClusterHealthAction(Client client, PluginSubject pluginSubject) { + this.client = client; + this.pluginSubject = pluginSubject; + } + + @Override + public List routes() { + return singletonList(new Route(GET, "/try-cluster-health")); + } + + @Override + public String getName() { + return "test_run_cluster_health_action"; + } + + @Override + public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + return new RestChannelConsumer() { + + @Override + public void accept(RestChannel channel) throws Exception { + pluginSubject.runAs(() -> { + ActionListener chr = 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))); }); + client.admin().cluster().health(new ClusterHealthRequest(), chr); + return null; + }); + } + }; + } +} diff --git a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java index bd0d7cec70..73ec6821f4 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -84,8 +84,10 @@ public List getRestHandlers( IndexNameExpressionResolver indexNameExpressionResolver, Supplier nodesInCluster ) { - TransportActionDependencies deps = new TransportActionDependencies(); - return List.of(new RestIndexDocumentIntoSystemIndexAction(client)); + return List.of( + new RestIndexDocumentIntoSystemIndexAction(client), + new RestRunClusterHealthAction(client, SECURITY_TRANSPORT_ACTION_DEPENDENCIES.getPluginSystemSubject()) + ); } @Override diff --git a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java index f2ed737959..82eeddfe01 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -273,6 +273,7 @@ private void evaluateSystemIndicesAccess( log.debug("Service account cannot access regular indices: {}", regularIndices); } presponse.allowed = false; + presponse.missingPrivileges.add(action); presponse.markComplete(); return; } From 22795df7cfe4e49fe0f53ae963457d6ea67a3188 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Aug 2024 16:31:14 -0400 Subject: [PATCH 12/49] Add bulk index test Signed-off-by: Craig Perkins --- .../opensearch/security/SystemIndexTests.java | 17 +++++ ...ulkIndexDocumentIntoSystemIndexAction.java | 70 +++++++++++++++++++ .../plugin/RestRunClusterHealthAction.java | 2 +- .../security/plugin/SystemIndexPlugin1.java | 3 +- 4 files changed, 90 insertions(+), 2 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 73063a1e2f..4ff00d0fe8 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -13,6 +13,7 @@ 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; @@ -64,6 +65,13 @@ public class SystemIndexTests { ) .build(); + @Before + public void wipeAllIndices() { + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + HttpResponse response = client.delete(".system-index1"); + } + } + @Test public void adminShouldNotBeAbleToDeleteSecurityIndex() { try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { @@ -123,4 +131,13 @@ public void testPluginShouldNotBeAbleToRunClusterActions() { ); } } + + @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())); + } + } } 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..85b89dde1c --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java @@ -0,0 +1,70 @@ +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.identity.PluginSubject; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestRequest; + +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 PluginSubject pluginSubject; + + public RestBulkIndexDocumentIntoSystemIndexAction(Client client, PluginSubject pluginSubject) { + this.client = client; + this.pluginSubject = pluginSubject; + } + + @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 { + pluginSubject.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/RestRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java index eef4cd99b9..faf061f66c 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java @@ -59,7 +59,7 @@ public void accept(RestChannel channel) throws Exception { 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)))); client.admin().cluster().health(new ClusterHealthRequest(), chr); return null; }); diff --git a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java index 73ec6821f4..0adb381908 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -86,7 +86,8 @@ public List getRestHandlers( ) { return List.of( new RestIndexDocumentIntoSystemIndexAction(client), - new RestRunClusterHealthAction(client, SECURITY_TRANSPORT_ACTION_DEPENDENCIES.getPluginSystemSubject()) + new RestRunClusterHealthAction(client, SECURITY_TRANSPORT_ACTION_DEPENDENCIES.getPluginSystemSubject()), + new RestBulkIndexDocumentIntoSystemIndexAction(client, SECURITY_TRANSPORT_ACTION_DEPENDENCIES.getPluginSystemSubject()) ); } From e075ae8d333bb94f1f84946ec1f4d80ffa136623 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 20 Aug 2024 16:52:42 -0400 Subject: [PATCH 13/49] Ensure bulk request with mix of actions blocks as expected Signed-off-by: Craig Perkins --- .../opensearch/security/SystemIndexTests.java | 22 +++++- ...dexDocumentIntoMixOfSystemIndexAction.java | 68 +++++++++++++++++++ .../security/plugin/SystemIndexPlugin1.java | 3 +- 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 4ff00d0fe8..656ba297bd 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -68,7 +68,8 @@ public class SystemIndexTests { @Before public void wipeAllIndices() { try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { - HttpResponse response = client.delete(".system-index1"); + client.delete(".system-index1"); + client.delete(".system-index2"); } } @@ -140,4 +141,23 @@ public void testPluginShouldBeAbleToBulkIndexDocumentIntoItsSystemIndex() { 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.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); + assertThat( + response.getBody(), + containsString( + "no permissions for [indices:data/write/bulk] and User [name=org.opensearch.security.plugin.SystemIndexPlugin1" + ) + ); + } + } } 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..ab56363a1d --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java @@ -0,0 +1,68 @@ +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.identity.PluginSubject; +import org.opensearch.rest.BaseRestHandler; +import org.opensearch.rest.BytesRestResponse; +import org.opensearch.rest.RestChannel; +import org.opensearch.rest.RestRequest; + +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 PluginSubject pluginSubject; + + public RestBulkIndexDocumentIntoMixOfSystemIndexAction(Client client, PluginSubject pluginSubject) { + this.client = client; + this.pluginSubject = pluginSubject; + } + + @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 { + pluginSubject.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/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java index 0adb381908..ff451bf448 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -87,7 +87,8 @@ public List getRestHandlers( return List.of( new RestIndexDocumentIntoSystemIndexAction(client), new RestRunClusterHealthAction(client, SECURITY_TRANSPORT_ACTION_DEPENDENCIES.getPluginSystemSubject()), - new RestBulkIndexDocumentIntoSystemIndexAction(client, SECURITY_TRANSPORT_ACTION_DEPENDENCIES.getPluginSystemSubject()) + new RestBulkIndexDocumentIntoSystemIndexAction(client, SECURITY_TRANSPORT_ACTION_DEPENDENCIES.getPluginSystemSubject()), + new RestBulkIndexDocumentIntoMixOfSystemIndexAction(client, SECURITY_TRANSPORT_ACTION_DEPENDENCIES.getPluginSystemSubject()) ); } From e12f6738847e72235897ea704039eaddec2bec6b Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 21 Aug 2024 11:19:15 -0400 Subject: [PATCH 14/49] Use subclass instead of attribute Signed-off-by: Craig Perkins --- .../opensearch/security/SystemIndexTests.java | 2 +- .../ContextProvidingPluginSubject.java | 5 ++- .../support/SafeSerializationUtils.java | 2 ++ .../opensearch/security/user/PluginUser.java | 34 +++++++++++++++++++ .../org/opensearch/security/user/User.java | 7 ++-- 5 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 src/main/java/org/opensearch/security/user/PluginUser.java diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 656ba297bd..7f7d608012 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -46,7 +46,7 @@ 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) diff --git a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java index 041a941e01..dd311d33e7 100644 --- a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java @@ -1,7 +1,6 @@ package org.opensearch.security.identity; import java.security.Principal; -import java.util.Map; import java.util.concurrent.Callable; import org.opensearch.common.util.concurrent.ThreadContext; @@ -9,6 +8,7 @@ import org.opensearch.identity.PluginSubject; import org.opensearch.plugins.Plugin; import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.PluginUser; import org.opensearch.security.user.User; import org.opensearch.threadpool.ThreadPool; @@ -21,8 +21,7 @@ public ContextProvidingPluginSubject(ThreadPool threadPool, Plugin plugin) { super(); this.threadPool = threadPool; this.pluginPrincipal = new NamedPrincipal(plugin.getClass().getCanonicalName()); - this.pluginUser = new User(pluginPrincipal.getName()); - this.pluginUser.addAttributes(Map.of("attr.internal.plugin", "true")); + this.pluginUser = new PluginUser(pluginPrincipal.getName()); } @Override diff --git a/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java b/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java index b58e4afd35..1d2289f0ae 100644 --- a/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java +++ b/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java @@ -26,6 +26,7 @@ import com.google.common.collect.ImmutableSet; import org.opensearch.security.auth.UserInjector; +import org.opensearch.security.user.PluginUser; import org.opensearch.security.user.User; import com.amazon.dlic.auth.ldap.LdapUser; @@ -48,6 +49,7 @@ public final class SafeSerializationUtils { InetSocketAddress.class, Pattern.class, User.class, + PluginUser.class, UserInjector.InjectedUser.class, SourceFieldsContext.class, LdapUser.class, diff --git a/src/main/java/org/opensearch/security/user/PluginUser.java b/src/main/java/org/opensearch/security/user/PluginUser.java new file mode 100644 index 0000000000..66ed1b0149 --- /dev/null +++ b/src/main/java/org/opensearch/security/user/PluginUser.java @@ -0,0 +1,34 @@ +/* + * 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.user; + +import java.io.IOException; + +import org.opensearch.core.common.io.stream.StreamInput; + +public class PluginUser extends User { + private static final long serialVersionUID = -4083322940729403322L; + + public PluginUser(StreamInput in) throws IOException { + super(in); + } + + /** + * Create a new plugin user without roles and attributes + * + * @param name The username (must not be null or empty) + * @throws IllegalArgumentException if name is null or empty + */ + public PluginUser(final String name) { + super(name, null, null); + } +} diff --git a/src/main/java/org/opensearch/security/user/User.java b/src/main/java/org/opensearch/security/user/User.java index fb3ef80853..67e557bf77 100644 --- a/src/main/java/org/opensearch/security/user/User.java +++ b/src/main/java/org/opensearch/security/user/User.java @@ -298,12 +298,9 @@ public boolean isServiceAccount() { } /** - * Check the custom attributes associated with this user - * - * @return true if it has a plugin attribute, otherwise false + * @return true if this instance is of the type PluginUser */ public boolean isPluginUser() { - Map userAttributesMap = this.getCustomAttributesMap(); - return userAttributesMap != null && "true".equals(userAttributesMap.get("attr.internal.plugin")); + return this instanceof PluginUser; } } From f9adcc443acf747a1bd99788bbbf3fcdf866915e Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 21 Aug 2024 14:22:08 -0400 Subject: [PATCH 15/49] Rename TransportActionDependencies to PluginContextSwitcher Signed-off-by: Craig Perkins --- ...dexDocumentIntoMixOfSystemIndexAction.java | 10 +++--- ...ulkIndexDocumentIntoSystemIndexAction.java | 10 +++--- .../plugin/RestRunClusterHealthAction.java | 10 +++--- .../security/plugin/SystemIndexPlugin1.java | 17 +++++---- ...ortIndexDocumentIntoSystemIndexAction.java | 11 +++--- .../identity/PluginContextSwitcher.java | 36 +++++++++++++++++++ .../identity/TransportActionDependencies.java | 17 --------- 7 files changed, 66 insertions(+), 45 deletions(-) create mode 100644 src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java delete mode 100644 src/main/java/org/opensearch/security/identity/TransportActionDependencies.java diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java index ab56363a1d..68a3c1decf 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java @@ -12,11 +12,11 @@ import org.opensearch.core.action.ActionListener; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.ToXContent; -import org.opensearch.identity.PluginSubject; 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; @@ -26,11 +26,11 @@ public class RestBulkIndexDocumentIntoMixOfSystemIndexAction extends BaseRestHandler { private final Client client; - private final PluginSubject pluginSubject; + private final PluginContextSwitcher contextSwitcher; - public RestBulkIndexDocumentIntoMixOfSystemIndexAction(Client client, PluginSubject pluginSubject) { + public RestBulkIndexDocumentIntoMixOfSystemIndexAction(Client client, PluginContextSwitcher contextSwitcher) { this.client = client; - this.pluginSubject = pluginSubject; + this.contextSwitcher = contextSwitcher; } @Override @@ -49,7 +49,7 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client @Override public void accept(RestChannel channel) throws Exception { - pluginSubject.runAs(() -> { + 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)); diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java index 85b89dde1c..74c4cb9d18 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java @@ -13,11 +13,11 @@ import org.opensearch.core.action.ActionListener; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.ToXContent; -import org.opensearch.identity.PluginSubject; 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; @@ -25,11 +25,11 @@ public class RestBulkIndexDocumentIntoSystemIndexAction extends BaseRestHandler { private final Client client; - private final PluginSubject pluginSubject; + private final PluginContextSwitcher contextSwitcher; - public RestBulkIndexDocumentIntoSystemIndexAction(Client client, PluginSubject pluginSubject) { + public RestBulkIndexDocumentIntoSystemIndexAction(Client client, PluginContextSwitcher contextSwitcher) { this.client = client; - this.pluginSubject = pluginSubject; + this.contextSwitcher = contextSwitcher; } @Override @@ -49,7 +49,7 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client @Override public void accept(RestChannel channel) throws Exception { - pluginSubject.runAs(() -> { + 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)); diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java index faf061f66c..d35fe2806e 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java @@ -19,11 +19,11 @@ import org.opensearch.core.action.ActionListener; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.ToXContent; -import org.opensearch.identity.PluginSubject; 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.GET; @@ -31,11 +31,11 @@ public class RestRunClusterHealthAction extends BaseRestHandler { private final Client client; - private final PluginSubject pluginSubject; + private final PluginContextSwitcher contextSwitcher; - public RestRunClusterHealthAction(Client client, PluginSubject pluginSubject) { + public RestRunClusterHealthAction(Client client, PluginContextSwitcher contextSwitcher) { this.client = client; - this.pluginSubject = pluginSubject; + this.contextSwitcher = contextSwitcher; } @Override @@ -54,7 +54,7 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client @Override public void accept(RestChannel channel) throws Exception { - pluginSubject.runAs(() -> { + contextSwitcher.runAs(() -> { ActionListener chr = ActionListener.wrap(r -> { channel.sendResponse( new BytesRestResponse(RestStatus.OK, r.toXContent(channel.newBuilder(), ToXContent.EMPTY_PARAMS)) diff --git a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java index ff451bf448..036c1fd6fd 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -39,14 +39,14 @@ import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; import org.opensearch.script.ScriptService; -import org.opensearch.security.identity.TransportActionDependencies; +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 static final TransportActionDependencies SECURITY_TRANSPORT_ACTION_DEPENDENCIES = new TransportActionDependencies(); + private PluginContextSwitcher contextSwitcher; private Client client; @@ -65,7 +65,8 @@ public Collection createComponents( Supplier repositoriesServiceSupplier ) { this.client = client; - return List.of(SECURITY_TRANSPORT_ACTION_DEPENDENCIES); + this.contextSwitcher = new PluginContextSwitcher(); + return List.of(contextSwitcher); } @Override @@ -86,9 +87,9 @@ public List getRestHandlers( ) { return List.of( new RestIndexDocumentIntoSystemIndexAction(client), - new RestRunClusterHealthAction(client, SECURITY_TRANSPORT_ACTION_DEPENDENCIES.getPluginSystemSubject()), - new RestBulkIndexDocumentIntoSystemIndexAction(client, SECURITY_TRANSPORT_ACTION_DEPENDENCIES.getPluginSystemSubject()), - new RestBulkIndexDocumentIntoMixOfSystemIndexAction(client, SECURITY_TRANSPORT_ACTION_DEPENDENCIES.getPluginSystemSubject()) + new RestRunClusterHealthAction(client, contextSwitcher), + new RestBulkIndexDocumentIntoSystemIndexAction(client, contextSwitcher), + new RestBulkIndexDocumentIntoMixOfSystemIndexAction(client, contextSwitcher) ); } @@ -101,6 +102,8 @@ public List getRestHandlers( @Override public void assignSubject(PluginSubject pluginSystemSubject) { - SECURITY_TRANSPORT_ACTION_DEPENDENCIES.setPluginSystemSubject(pluginSystemSubject); + if (contextSwitcher != null) { + this.contextSwitcher.initialize(pluginSystemSubject); + } } } diff --git a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java index 39cbfa3656..93347a1515 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java @@ -19,8 +19,7 @@ import org.opensearch.common.inject.Inject; import org.opensearch.common.xcontent.XContentType; import org.opensearch.core.action.ActionListener; -import org.opensearch.identity.PluginSubject; -import org.opensearch.security.identity.TransportActionDependencies; +import org.opensearch.security.identity.PluginContextSwitcher; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.user.User; import org.opensearch.tasks.Task; @@ -33,7 +32,7 @@ public class TransportIndexDocumentIntoSystemIndexAction extends HandledTranspor private final Client client; private final ThreadPool threadPool; - private final PluginSubject pluginSystemSubject; + private final PluginContextSwitcher contextSwitcher; @Inject public TransportIndexDocumentIntoSystemIndexAction( @@ -41,12 +40,12 @@ public TransportIndexDocumentIntoSystemIndexAction( final ActionFilters actionFilters, final Client client, final ThreadPool threadPool, - final TransportActionDependencies deps + final PluginContextSwitcher contextSwitcher ) { super(IndexDocumentIntoSystemIndexAction.NAME, transportService, actionFilters, IndexDocumentIntoSystemIndexRequest::new); this.client = client; this.threadPool = threadPool; - this.pluginSystemSubject = deps.getPluginSystemSubject(); + this.contextSwitcher = contextSwitcher; } @Override @@ -57,7 +56,7 @@ protected void doExecute( ) { String indexName = request.getIndexName(); try { - pluginSystemSubject.runAs(() -> { + contextSwitcher.runAs(() -> { client.admin().indices().create(new CreateIndexRequest(indexName), ActionListener.wrap(r -> { client.index( new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) 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..52809d4fab --- /dev/null +++ b/src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java @@ -0,0 +1,36 @@ +/* + * 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.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) { + if (pluginSubject == null) { + return null; + } + try { + return pluginSubject.runAs(callable); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/org/opensearch/security/identity/TransportActionDependencies.java b/src/main/java/org/opensearch/security/identity/TransportActionDependencies.java deleted file mode 100644 index 4a778641c6..0000000000 --- a/src/main/java/org/opensearch/security/identity/TransportActionDependencies.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.opensearch.security.identity; - -import org.opensearch.identity.PluginSubject; - -public class TransportActionDependencies { - private PluginSubject pluginSystemSubject; - - public TransportActionDependencies() {} - - public void setPluginSystemSubject(PluginSubject pluginSystemSubject) { - this.pluginSystemSubject = pluginSystemSubject; - } - - public PluginSubject getPluginSystemSubject() { - return pluginSystemSubject; - } -} From 2074dab07f4fec044d80e811f82ab5a50fdd8004 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 22 Aug 2024 09:30:54 -0400 Subject: [PATCH 16/49] Prevent pluginUser from being serialized to nodes before 2.17.0 for backwards compatibility Signed-off-by: Craig Perkins --- .../security/transport/SecurityInterceptor.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java index f55d9ac338..b73118fc91 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java +++ b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java @@ -39,6 +39,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.opensearch.Version; import org.opensearch.action.admin.cluster.shards.ClusterSearchShardsAction; import org.opensearch.action.admin.cluster.shards.ClusterSearchShardsResponse; import org.opensearch.action.get.GetRequest; @@ -128,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.17.0 + if (user0 != null && user0.isPluginUser() && connection.getVersion().before(Version.V_2_17_0)) { + user0 = null; + } + return user0; + } + public void sendRequestDecorate( AsyncSender sender, Connection connection, @@ -138,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( @@ -310,7 +320,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); From 0960ce09fe2edda7f18d6638764e5b2c9034f8c6 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 22 Aug 2024 15:03:06 -0400 Subject: [PATCH 17/49] Demonstrate how a role can be created in-memory to re-use authz Signed-off-by: Craig Perkins --- .../opensearch/security/SystemIndexTests.java | 3 +- .../security/OpenSearchSecurityPlugin.java | 18 ++++++++++-- .../ContextProvidingPluginSubject.java | 25 ++++++++++++++-- .../identity/SystemIndexRegistry.java | 21 ++++++++++++++ .../privileges/PrivilegesEvaluator.java | 29 +++++++++++++++++-- .../SystemIndexAccessEvaluator.java | 21 ++------------ .../security/securityconf/ConfigModelV6.java | 27 +++++++++++++++++ .../security/securityconf/ConfigModelV7.java | 28 ++++++++++++++++++ .../security/securityconf/SecurityRoles.java | 8 +++++ 9 files changed, 152 insertions(+), 28 deletions(-) create mode 100644 src/main/java/org/opensearch/security/identity/SystemIndexRegistry.java diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 7f7d608012..150448dab3 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -151,11 +151,10 @@ public void testPluginShouldNotBeAbleToBulkIndexDocumentIntoMixOfSystemIndexWher try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { HttpResponse response = client.put("try-create-and-bulk-mixed-index"); - assertThat(response.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); assertThat( response.getBody(), containsString( - "no permissions for [indices:data/write/bulk] and User [name=org.opensearch.security.plugin.SystemIndexPlugin1" + "no permissions for [indices:data/write/bulk[s]] and User [name=org.opensearch.security.plugin.SystemIndexPlugin1" ) ); } diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index d5eddb0af6..63222c5c59 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -124,6 +124,7 @@ import org.opensearch.plugins.SecureHttpTransportSettingsProvider; import org.opensearch.plugins.SecureSettingsFactory; import org.opensearch.plugins.SecureTransportSettingsProvider; +import org.opensearch.plugins.SystemIndexPlugin; import org.opensearch.repositories.RepositoriesService; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; @@ -168,6 +169,7 @@ import org.opensearch.security.http.XFFResolver; import org.opensearch.security.identity.ContextProvidingPluginSubject; import org.opensearch.security.identity.SecurityTokenManager; +import org.opensearch.security.identity.SystemIndexRegistry; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.privileges.PrivilegesInterceptor; import org.opensearch.security.privileges.RestLayerPrivilegesEvaluator; @@ -268,6 +270,7 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin private volatile Salt salt; private volatile OpensearchDynamicSetting transportPassiveAuthSetting; private volatile PasswordHasher passwordHasher; + private volatile SystemIndexRegistry systemIndexRegistry; public static boolean isActionTraceEnabled() { @@ -1117,6 +1120,8 @@ public Collection createComponents( final CompatConfig compatConfig = new CompatConfig(environment, transportPassiveAuthSetting); + systemIndexRegistry = new SystemIndexRegistry(); + evaluator = new PrivilegesEvaluator( clusterService, threadPool, @@ -1127,7 +1132,8 @@ public Collection createComponents( privilegesInterceptor, cih, irr, - namedXContentRegistry.get() + namedXContentRegistry.get(), + systemIndexRegistry ); sf = new SecurityFilter(settings, evaluator, adminDns, dlsFlsValve, auditLog, threadPool, cs, compatConfig, irr, xffResolver); @@ -1206,6 +1212,7 @@ public Collection createComponents( components.add(dcf); components.add(userService); components.add(passwordHasher); + components.add(systemIndexRegistry); if (!ExternalSecurityKeyStore.hasExternalSslContext(settings)) { components.add(sks); @@ -2117,7 +2124,14 @@ public SecurityTokenManager getTokenManager() { @Override public PluginSubject getPluginSubject(Plugin plugin) { - return new ContextProvidingPluginSubject(threadPool, plugin); + if (systemIndexRegistry != null) { + Collection systemIndexDescriptors = ((SystemIndexPlugin) plugin).getSystemIndexDescriptors(settings); + Set systemIndexPatterns = systemIndexDescriptors.stream() + .map(SystemIndexDescriptor::getIndexPattern) + .collect(Collectors.toSet()); + systemIndexRegistry.addSystemIntexPatterns(plugin.getClass().getCanonicalName(), systemIndexPatterns); + } + return new ContextProvidingPluginSubject(threadPool, settings, plugin); } @Override diff --git a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java index dd311d33e7..0204a39898 100644 --- a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java @@ -1,27 +1,46 @@ package org.opensearch.security.identity; import java.security.Principal; +import java.util.Collection; +import java.util.List; 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.indices.SystemIndexDescriptor; import org.opensearch.plugins.Plugin; +import org.opensearch.plugins.SystemIndexPlugin; +import org.opensearch.security.securityconf.impl.v7.RoleV7; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.user.PluginUser; -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; + private final PluginUser pluginUser; + private final RoleV7 roleV7; - public ContextProvidingPluginSubject(ThreadPool threadPool, Plugin plugin) { + public ContextProvidingPluginSubject(ThreadPool threadPool, Settings settings, Plugin plugin) { super(); this.threadPool = threadPool; this.pluginPrincipal = new NamedPrincipal(plugin.getClass().getCanonicalName()); this.pluginUser = new PluginUser(pluginPrincipal.getName()); + if (plugin instanceof SystemIndexPlugin) { + Collection systemIndexDescriptors = ((SystemIndexPlugin) plugin).getSystemIndexDescriptors(settings); + roleV7 = new RoleV7(); + if (systemIndexDescriptors != null) { + List systemIndexPatterns = systemIndexDescriptors.stream().map(SystemIndexDescriptor::getIndexPattern).toList(); + RoleV7.Index indexPermissions = new RoleV7.Index(); + indexPermissions.setIndex_patterns(systemIndexPatterns); + indexPermissions.setAllowed_actions(List.of(ConfigConstants.SYSTEM_INDEX_PERMISSION)); + roleV7.setIndex_permissions(List.of(indexPermissions)); + } + } else { + roleV7 = null; + } } @Override diff --git a/src/main/java/org/opensearch/security/identity/SystemIndexRegistry.java b/src/main/java/org/opensearch/security/identity/SystemIndexRegistry.java new file mode 100644 index 0000000000..8fbb34dfc0 --- /dev/null +++ b/src/main/java/org/opensearch/security/identity/SystemIndexRegistry.java @@ -0,0 +1,21 @@ +package org.opensearch.security.identity; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class SystemIndexRegistry { + private final Map> registeredSystemIndexPatterns; + + public SystemIndexRegistry() { + registeredSystemIndexPatterns = new HashMap<>(); + } + + public void addSystemIntexPatterns(String pluginIdentifier, Set indexPatterns) { + registeredSystemIndexPatterns.put(pluginIdentifier, indexPatterns); + } + + public Set getSystemIndexPatterns(String pluginIdentifier) { + return registeredSystemIndexPatterns.get(pluginIdentifier); + } +} diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index c6ea139b3e..7739603139 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; @@ -85,6 +86,7 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.ClusterInfoHolder; import org.opensearch.security.configuration.ConfigurationRepository; +import org.opensearch.security.identity.SystemIndexRegistry; import org.opensearch.security.resolver.IndexResolverReplacer; import org.opensearch.security.resolver.IndexResolverReplacer.Resolved; import org.opensearch.security.securityconf.ConfigModel; @@ -142,6 +144,8 @@ public class PrivilegesEvaluator { private final PitPrivilegesEvaluator pitPrivilegesEvaluator; private DynamicConfigModel dcm; private final NamedXContentRegistry namedXContentRegistry; + private final SystemIndexRegistry systemIndexRegistry; + private final Map pluginRoles; public PrivilegesEvaluator( final ClusterService clusterService, @@ -153,7 +157,8 @@ public PrivilegesEvaluator( final PrivilegesInterceptor privilegesInterceptor, final ClusterInfoHolder clusterInfoHolder, final IndexResolverReplacer irr, - NamedXContentRegistry namedXContentRegistry + NamedXContentRegistry namedXContentRegistry, + final SystemIndexRegistry systemIndexRegistry ) { super(); @@ -163,6 +168,8 @@ public PrivilegesEvaluator( this.threadContext = threadPool.getThreadContext(); this.privilegesInterceptor = privilegesInterceptor; + this.systemIndexRegistry = systemIndexRegistry; + this.pluginRoles = new HashMap<>(); this.checkSnapshotRestoreWritePrivileges = settings.getAsBoolean( ConfigConstants.SECURITY_CHECK_SNAPSHOT_RESTORE_WRITE_PRIVILEGES, @@ -193,6 +200,17 @@ public SecurityRoles getSecurityRoles(Set roles) { return configModel.getSecurityRoles().filter(roles); } + public SecurityRoles getSecurityRoleForPlugin(String pluginIdentifier) { + SecurityRoles pluginRole = pluginRoles.get(pluginIdentifier); + if (pluginRole == null) { + Set systemIndexPatterns = systemIndexRegistry.getSystemIndexPatterns(pluginIdentifier); + pluginRole = configModel.getSecurityRoles() + .createSecurityRole(pluginIdentifier, Set.of(BulkAction.NAME), Set.of(), Set.of(), systemIndexPatterns); + 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); @@ -279,7 +297,12 @@ public PrivilegesEvaluatorResponse evaluate(PrivilegesEvaluationContext context) context.setMappedRoles(mappedRoles); } presponse.resolvedSecurityRoles.addAll(mappedRoles); - final SecurityRoles securityRoles = getSecurityRoles(mappedRoles); + final SecurityRoles securityRoles; + if (user.isPluginUser()) { + securityRoles = getSecurityRoleForPlugin(user.getName()); + } else { + securityRoles = getSecurityRoles(mappedRoles); + } // Add the security roles for this user so that they can be used for DLS parameter substitution. user.addSecurityRoles(mappedRoles); @@ -292,7 +315,7 @@ public PrivilegesEvaluatorResponse evaluate(PrivilegesEvaluationContext context) log.debug("Mapped roles: {}", mappedRoles.toString()); } - if (request instanceof BulkRequest && (Strings.isNullOrEmpty(user.getRequestedTenant())) && !user.isPluginUser()) { + if (request instanceof BulkRequest && (Strings.isNullOrEmpty(user.getRequestedTenant()))) { // Shortcut for bulk actions. The details are checked on the lower level of the BulkShardRequests (Action // indices:data/write/bulk[s]). // This shortcut is only possible if the default tenant is selected, as we might need to rewrite the request for non-default diff --git a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java index 82eeddfe01..affe6177b5 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -255,10 +255,9 @@ private void evaluateSystemIndicesAccess( boolean containsSystemIndex = requestContainsAnySystemIndices(requestedResolved); boolean containsRegularIndex = requestContainsAnyRegularIndices(requestedResolved); boolean serviceAccountUser = user.isServiceAccount(); - boolean pluginUser = user.isPluginUser(); if (isSystemIndexPermissionEnabled) { - if ((serviceAccountUser || pluginUser) && containsRegularIndex) { + if (serviceAccountUser && containsRegularIndex) { auditLog.logSecurityIndexAttempt(request, action, task); if (!containsSystemIndex && log.isInfoEnabled()) { log.info("{} not permitted for a service account {} on non-system indices.", action, securityRoles); @@ -291,22 +290,6 @@ private void evaluateSystemIndicesAccess( presponse.allowed = false; presponse.markComplete(); return; - } else if (containsSystemIndex && pluginUser) { - boolean requestContainsOnlyPluginsRegisteredSystemIndices = requestedResolved.getAllIndices() - .equals(SystemIndexRegistry.matchesPluginSystemIndexPattern(user.getName(), requestedResolved.getAllIndices())); - if (requestContainsOnlyPluginsRegisteredSystemIndices) { - // allow all action types, but ensure that request only contains registered system indices for this plugin - presponse.allowed = true; - } else { - auditLog.logSecurityIndexAttempt(request, action, task); - if (log.isInfoEnabled()) { - log.info("{} can only interact with its own system indices", user.getName()); - } - presponse.missingPrivileges.add(action); - presponse.allowed = false; - } - presponse.markComplete(); - return; } else if (containsSystemIndex && !securityRoles.hasExplicitIndexPermission( requestedResolved, @@ -324,7 +307,9 @@ private void evaluateSystemIndicesAccess( String.join(", ", getAllSystemIndices(requestedResolved)) ); } + System.out.println("Not authorized"); presponse.allowed = false; + presponse.missingPrivileges.add(action); presponse.markComplete(); return; } diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java index e35fb40a24..6157c9deef 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java @@ -359,6 +359,33 @@ public SecurityRoles filter(Set keep) { return retVal; } + @Override + public SecurityRoles createSecurityRole( + String roleName, + Set clusterPerms, + Set indexPatterns, + Set allowedActions, + Set systemIndexPatterns + ) { + SecurityRole role = new SecurityRole(roleName); + role.addClusterPerms(clusterPerms); + for (String ip : indexPatterns) { + IndexPattern idxPattern = new IndexPattern(ip); + TypePerm perms = new TypePerm(""); + perms.addPerms(allowedActions); + idxPattern.addTypePerms(perms); + } + for (String ip : systemIndexPatterns) { + IndexPattern idxPattern = new IndexPattern(ip); + TypePerm perms = new TypePerm(""); + perms.addPerms(Set.of("*", ConfigConstants.SYSTEM_INDEX_PERMISSION)); + idxPattern.addTypePerms(perms); + } + SecurityRoles roles = new SecurityRoles(1); + roles.addSecurityRole(role); + return roles; + } + @Override public EvaluatedDlsFlsConfig getDlsFls( User user, diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index f78c173202..82507f3815 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -273,6 +273,31 @@ public SecurityRoles filter(Set keep) { return retVal; } + @Override + public SecurityRoles createSecurityRole( + String roleName, + Set clusterPerms, + Set indexPatterns, + Set allowedActions, + Set systemIndexPatterns + ) { + Set ipatterns = new HashSet<>(); + for (String ip : indexPatterns) { + IndexPattern idxPattern = new IndexPattern(ip); + idxPattern.addPerm(allowedActions); + ipatterns.add(idxPattern); + } + for (String ip : systemIndexPatterns) { + IndexPattern idxPattern = new IndexPattern(ip); + idxPattern.addPerm(Set.of("*", ConfigConstants.SYSTEM_INDEX_PERMISSION)); + ipatterns.add(idxPattern); + } + SecurityRole role = new SecurityRole(roleName, ipatterns, WildcardMatcher.from(clusterPerms)); + SecurityRoles roles = new SecurityRoles(1); + roles.addSecurityRole(role); + return roles; + } + @Override public EvaluatedDlsFlsConfig getDlsFls( User user, @@ -439,6 +464,7 @@ public boolean hasExplicitIndexPermission( ) { final Set indicesForRequest = new HashSet<>(resolved.getAllIndicesResolved(cs, resolver)); + System.out.println("indicesForRequest: " + indicesForRequest); if (indicesForRequest.isEmpty()) { // If no indices could be found on the request there is no way to check for the explicit permissions return false; @@ -449,6 +475,8 @@ public boolean hasExplicitIndexPermission( .flatMap(Collection::stream) .collect(Collectors.toSet()); + System.out.println("explicitlyAllowedIndices: " + explicitlyAllowedIndices); + if (log.isDebugEnabled()) { log.debug( "ExplicitIndexPermission check indices for request {}, explicitly allowed indices {}", diff --git a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java index fb25e1a21f..9068b37aa6 100644 --- a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java +++ b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java @@ -97,5 +97,13 @@ Set getAllPermittedIndicesForDashboards( SecurityRoles filter(Set roles); + SecurityRoles createSecurityRole( + String roleName, + Set clusterPerms, + Set indexPatterns, + Set allowedActions, + Set systemIndexPatterns + ); + boolean isPermittedOnSystemIndex(String indexName); } From 8c2b7ba06453a6ba691db089081b76ff3ffa7185 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 22 Aug 2024 15:59:46 -0400 Subject: [PATCH 18/49] Add missing license headers Signed-off-by: Craig Perkins --- ...estBulkIndexDocumentIntoMixOfSystemIndexAction.java | 10 ++++++++++ .../RestBulkIndexDocumentIntoSystemIndexAction.java | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java index 68a3c1decf..0d1dc4fe01 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java @@ -1,3 +1,13 @@ +/* + * 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; diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java index 74c4cb9d18..56fa3ef2a9 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java @@ -1,3 +1,13 @@ +/* + * 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; From 675f20aedef4bc815452c360cd84ff07f1e8bdcc Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 22 Aug 2024 17:54:35 -0400 Subject: [PATCH 19/49] Remove unused code Signed-off-by: Craig Perkins --- .../identity/ContextProvidingPluginSubject.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java index 0204a39898..5cd71dfc39 100644 --- a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java @@ -1,17 +1,13 @@ package org.opensearch.security.identity; import java.security.Principal; -import java.util.Collection; -import java.util.List; 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.indices.SystemIndexDescriptor; import org.opensearch.plugins.Plugin; -import org.opensearch.plugins.SystemIndexPlugin; import org.opensearch.security.securityconf.impl.v7.RoleV7; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.user.PluginUser; @@ -28,19 +24,6 @@ public ContextProvidingPluginSubject(ThreadPool threadPool, Settings settings, P this.threadPool = threadPool; this.pluginPrincipal = new NamedPrincipal(plugin.getClass().getCanonicalName()); this.pluginUser = new PluginUser(pluginPrincipal.getName()); - if (plugin instanceof SystemIndexPlugin) { - Collection systemIndexDescriptors = ((SystemIndexPlugin) plugin).getSystemIndexDescriptors(settings); - roleV7 = new RoleV7(); - if (systemIndexDescriptors != null) { - List systemIndexPatterns = systemIndexDescriptors.stream().map(SystemIndexDescriptor::getIndexPattern).toList(); - RoleV7.Index indexPermissions = new RoleV7.Index(); - indexPermissions.setIndex_patterns(systemIndexPatterns); - indexPermissions.setAllowed_actions(List.of(ConfigConstants.SYSTEM_INDEX_PERMISSION)); - roleV7.setIndex_permissions(List.of(indexPermissions)); - } - } else { - roleV7 = null; - } } @Override From 8d3eb1c451b151f3d55ae7ac2a0a24bf9482e402 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 23 Aug 2024 11:48:04 -0400 Subject: [PATCH 20/49] Remove unused field Signed-off-by: Craig Perkins --- .../security/identity/ContextProvidingPluginSubject.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java index 5cd71dfc39..63ed349ca1 100644 --- a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java @@ -8,7 +8,6 @@ import org.opensearch.identity.NamedPrincipal; import org.opensearch.identity.PluginSubject; import org.opensearch.plugins.Plugin; -import org.opensearch.security.securityconf.impl.v7.RoleV7; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.user.PluginUser; import org.opensearch.threadpool.ThreadPool; @@ -17,7 +16,6 @@ public class ContextProvidingPluginSubject implements PluginSubject { private final ThreadPool threadPool; private final NamedPrincipal pluginPrincipal; private final PluginUser pluginUser; - private final RoleV7 roleV7; public ContextProvidingPluginSubject(ThreadPool threadPool, Settings settings, Plugin plugin) { super(); From 115fa0e01022462e83eab9bd0ebe9dc2fbda1765 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 27 Aug 2024 17:12:52 -0400 Subject: [PATCH 21/49] Implement getCurrentSubject Signed-off-by: Craig Perkins --- .../opensearch/security/SystemIndexTests.java | 14 +++++- .../plugin/RestRunClusterHealthAction.java | 38 +++++++++++---- .../security/plugin/SystemIndexPlugin1.java | 47 +++++++++++++++++++ .../security/OpenSearchSecurityPlugin.java | 5 +- .../security/auth/BackendRegistry.java | 12 ++++- .../identity/SecurityUserSubject.java | 42 +++++++++++++++++ .../security/support/ConfigConstants.java | 1 + 7 files changed, 143 insertions(+), 16 deletions(-) create mode 100644 src/main/java/org/opensearch/security/identity/SecurityUserSubject.java diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 150448dab3..647973cedb 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -45,8 +45,9 @@ public class SystemIndexTests { public static final AuthcDomain AUTHC_DOMAIN = new AuthcDomain("basic", 0).httpAuthenticatorWithChallenge("basic").backend("internal"); + // TODO Change this from SINGLENODE to default to test with multiple nodes @ClassRule - public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.DEFAULT) + public static final LocalCluster cluster = new LocalCluster.Builder().clusterManager(ClusterManager.SINGLENODE) .anonymousAuth(false) .authc(AUTHC_DOMAIN) .users(USER_ADMIN) @@ -121,7 +122,7 @@ public void testPluginShouldNotBeAbleToIndexDocumentIntoSystemIndexRegisteredByO @Test public void testPluginShouldNotBeAbleToRunClusterActions() { try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { - HttpResponse response = client.get("try-cluster-health/"); + HttpResponse response = client.get("try-cluster-health/plugin"); assertThat(response.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); assertThat( @@ -133,6 +134,15 @@ public void testPluginShouldNotBeAbleToRunClusterActions() { } } + @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 testPluginShouldBeAbleToBulkIndexDocumentIntoItsSystemIndex() { try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java index d35fe2806e..26747abc02 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java @@ -19,6 +19,8 @@ import org.opensearch.core.action.ActionListener; import org.opensearch.core.rest.RestStatus; import org.opensearch.core.xcontent.ToXContent; +import org.opensearch.identity.IdentityService; +import org.opensearch.identity.Subject; import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; @@ -40,7 +42,7 @@ public RestRunClusterHealthAction(Client client, PluginContextSwitcher contextSw @Override public List routes() { - return singletonList(new Route(GET, "/try-cluster-health")); + return singletonList(new Route(GET, "/try-cluster-health/{runAs}")); } @Override @@ -50,19 +52,35 @@ public String getName() { @Override public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + String runAs = request.param("runAs"); + return new RestChannelConsumer() { @Override public void accept(RestChannel channel) throws Exception { - contextSwitcher.runAs(() -> { - ActionListener chr = 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)))); - client.admin().cluster().health(new ClusterHealthRequest(), chr); - return null; - }); + if ("user".equalsIgnoreCase(runAs)) { + IdentityService identityService = SystemIndexPlugin1.GuiceHolder.getIdentityService(); + Subject user = identityService.getCurrentSubject(); + user.runAs(() -> { + ActionListener chr = 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)))); + client.admin().cluster().health(new ClusterHealthRequest(), chr); + return null; + }); + } else { + contextSwitcher.runAs(() -> { + ActionListener chr = 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)))); + client.admin().cluster().health(new ClusterHealthRequest(), chr); + return null; + }); + } } }; } diff --git a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java index 036c1fd6fd..cc5b801b1f 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -10,6 +10,7 @@ package org.opensearch.security.plugin; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -21,6 +22,10 @@ import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.node.DiscoveryNodes; import org.opensearch.cluster.service.ClusterService; +import org.opensearch.common.inject.Inject; +import org.opensearch.common.lifecycle.Lifecycle; +import org.opensearch.common.lifecycle.LifecycleComponent; +import org.opensearch.common.lifecycle.LifecycleListener; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.IndexScopedSettings; import org.opensearch.common.settings.Settings; @@ -30,6 +35,7 @@ import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; +import org.opensearch.identity.IdentityService; import org.opensearch.identity.PluginSubject; import org.opensearch.indices.SystemIndexDescriptor; import org.opensearch.plugins.IdentityAwarePlugin; @@ -106,4 +112,45 @@ public void assignSubject(PluginSubject pluginSystemSubject) { this.contextSwitcher.initialize(pluginSystemSubject); } } + + @Override + public Collection> getGuiceServiceClasses() { + final List> services = new ArrayList<>(1); + services.add(GuiceHolder.class); + return services; + } + + public static class GuiceHolder implements LifecycleComponent { + private static IdentityService identityService; + + @Inject + public GuiceHolder(IdentityService identityService) { + GuiceHolder.identityService = identityService; + } + + public static IdentityService getIdentityService() { + return identityService; + } + + @Override + public void close() {} + + @Override + public Lifecycle.State lifecycleState() { + return null; + } + + @Override + public void addLifecycleListener(LifecycleListener listener) {} + + @Override + public void removeLifecycleListener(LifecycleListener listener) {} + + @Override + public void start() {} + + @Override + public void stop() {} + + } } diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 63222c5c59..a6286a6121 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; @@ -223,6 +222,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_SUBJECT; 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; @@ -2113,8 +2113,7 @@ private static String handleKeyword(final String field) { @Override public Subject getCurrentSubject() { - // Not supported - return new NoopSubject(); + return (Subject) threadPool.getThreadContext().getPersistent(OPENDISTRO_SECURITY_SUBJECT); } @Override diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index 0e39acf59e..dce8087ea3 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; @@ -63,6 +64,7 @@ import org.opensearch.security.filter.SecurityRequestChannel; import org.opensearch.security.filter.SecurityResponse; import org.opensearch.security.http.XFFResolver; +import org.opensearch.security.identity.SecurityUserSubject; import org.opensearch.security.securityconf.DynamicConfigModel; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.WildcardMatcher; @@ -222,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 SecurityUserSubject(threadPool, superuser); + threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_SUBJECT, subject); + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, superuser); auditLog.logSucceededLogin(sslPrincipal, true, null, request); return true; } @@ -385,6 +390,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 SecurityUserSubject(threadPool, impersonatedUser == null ? authenticatedUser : impersonatedUser); + threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_SUBJECT, subject); auditLog.logSucceededLogin( (impersonatedUser == null ? authenticatedUser : impersonatedUser).getName(), false, @@ -417,7 +424,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 SecurityUserSubject(threadPool, anonymousUser); + threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, anonymousUser); + threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_SUBJECT, 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/identity/SecurityUserSubject.java b/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java new file mode 100644 index 0000000000..4fe1732f86 --- /dev/null +++ b/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java @@ -0,0 +1,42 @@ +package org.opensearch.security.identity; + +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 SecurityUserSubject implements UserSubject { + private final NamedPrincipal userPrincipal; + private final ThreadPool threadPool; + private final User user; + + public SecurityUserSubject(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/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index 11b3ac48ac..ab99275897 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_SUBJECT = OPENDISTRO_SECURITY_CONFIG_PREFIX + "subject"; 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"; From f6650eb86071020eeda20ed6d2478992a8e30eba Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 27 Aug 2024 22:36:32 -0400 Subject: [PATCH 22/49] Use transport for cluster health tests Signed-off-by: Craig Perkins --- .../opensearch/security/SystemIndexTests.java | 3 +- .../plugin/RestRunClusterHealthAction.java | 42 +--------- .../plugin/RunClusterHealthAction.java | 22 ++++++ .../plugin/RunClusterHealthRequest.java | 40 ++++++++++ .../plugin/RunClusterHealthResponse.java | 40 ++++++++++ .../security/plugin/SystemIndexPlugin1.java | 50 +----------- .../TransportRunClusterHealthAction.java | 78 +++++++++++++++++++ .../security/OpenSearchSecurityPlugin.java | 1 + .../security/securityconf/ConfigModelV7.java | 3 - .../transport/SecuritySSLRequestHandler.java | 2 +- .../transport/SecurityRequestHandler.java | 13 ++-- 11 files changed, 196 insertions(+), 98 deletions(-) create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthAction.java create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthRequest.java create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthResponse.java create mode 100644 src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.java diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 647973cedb..304d48ea70 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -45,9 +45,8 @@ public class SystemIndexTests { public static final AuthcDomain AUTHC_DOMAIN = new AuthcDomain("basic", 0).httpAuthenticatorWithChallenge("basic").backend("internal"); - // TODO Change this from SINGLENODE to default to test with multiple nodes @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) diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java index 26747abc02..755f3278f0 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java @@ -12,19 +12,11 @@ import java.util.List; -import org.opensearch.action.admin.cluster.health.ClusterHealthRequest; -import org.opensearch.action.admin.cluster.health.ClusterHealthResponse; import org.opensearch.client.Client; import org.opensearch.client.node.NodeClient; -import org.opensearch.core.action.ActionListener; -import org.opensearch.core.rest.RestStatus; -import org.opensearch.core.xcontent.ToXContent; -import org.opensearch.identity.IdentityService; -import org.opensearch.identity.Subject; import org.opensearch.rest.BaseRestHandler; -import org.opensearch.rest.BytesRestResponse; -import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; +import org.opensearch.rest.action.RestToXContentListener; import org.opensearch.security.identity.PluginContextSwitcher; import static java.util.Collections.singletonList; @@ -53,35 +45,7 @@ public String getName() { @Override public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { String runAs = request.param("runAs"); - - return new RestChannelConsumer() { - - @Override - public void accept(RestChannel channel) throws Exception { - if ("user".equalsIgnoreCase(runAs)) { - IdentityService identityService = SystemIndexPlugin1.GuiceHolder.getIdentityService(); - Subject user = identityService.getCurrentSubject(); - user.runAs(() -> { - ActionListener chr = 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)))); - client.admin().cluster().health(new ClusterHealthRequest(), chr); - return null; - }); - } else { - contextSwitcher.runAs(() -> { - ActionListener chr = 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)))); - client.admin().cluster().health(new ClusterHealthRequest(), chr); - return null; - }); - } - } - }; + 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..e171d9213a --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthResponse.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.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; + +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 index cc5b801b1f..3f1112c4b6 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -10,7 +10,6 @@ package org.opensearch.security.plugin; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -22,10 +21,6 @@ import org.opensearch.cluster.metadata.IndexNameExpressionResolver; import org.opensearch.cluster.node.DiscoveryNodes; import org.opensearch.cluster.service.ClusterService; -import org.opensearch.common.inject.Inject; -import org.opensearch.common.lifecycle.Lifecycle; -import org.opensearch.common.lifecycle.LifecycleComponent; -import org.opensearch.common.lifecycle.LifecycleListener; import org.opensearch.common.settings.ClusterSettings; import org.opensearch.common.settings.IndexScopedSettings; import org.opensearch.common.settings.Settings; @@ -35,7 +30,6 @@ import org.opensearch.core.xcontent.NamedXContentRegistry; import org.opensearch.env.Environment; import org.opensearch.env.NodeEnvironment; -import org.opensearch.identity.IdentityService; import org.opensearch.identity.PluginSubject; import org.opensearch.indices.SystemIndexDescriptor; import org.opensearch.plugins.IdentityAwarePlugin; @@ -102,7 +96,8 @@ public List getRestHandlers( @Override public List> getActions() { return Arrays.asList( - new ActionHandler<>(IndexDocumentIntoSystemIndexAction.INSTANCE, TransportIndexDocumentIntoSystemIndexAction.class) + new ActionHandler<>(IndexDocumentIntoSystemIndexAction.INSTANCE, TransportIndexDocumentIntoSystemIndexAction.class), + new ActionHandler<>(RunClusterHealthAction.INSTANCE, TransportRunClusterHealthAction.class) ); } @@ -112,45 +107,4 @@ public void assignSubject(PluginSubject pluginSystemSubject) { this.contextSwitcher.initialize(pluginSystemSubject); } } - - @Override - public Collection> getGuiceServiceClasses() { - final List> services = new ArrayList<>(1); - services.add(GuiceHolder.class); - return services; - } - - public static class GuiceHolder implements LifecycleComponent { - private static IdentityService identityService; - - @Inject - public GuiceHolder(IdentityService identityService) { - GuiceHolder.identityService = identityService; - } - - public static IdentityService getIdentityService() { - return identityService; - } - - @Override - public void close() {} - - @Override - public Lifecycle.State lifecycleState() { - return null; - } - - @Override - public void addLifecycleListener(LifecycleListener listener) {} - - @Override - public void removeLifecycleListener(LifecycleListener listener) {} - - @Override - public void start() {} - - @Override - public void stop() {} - - } } 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..98264d6638 --- /dev/null +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.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 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 { + contextSwitcher.runAs(() -> { + ActionListener chr = ActionListener.wrap( + r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, + actionListener::onFailure + ); + client.admin().cluster().health(new ClusterHealthRequest(), chr); + return null; + }); + } + } +} diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index a6286a6121..5f9ec458bb 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -1053,6 +1053,7 @@ public Collection createComponents( } this.threadPool = threadPool; + System.out.println("ThreadPool createComponents: " + threadPool); this.cs = clusterService; this.localClient = localClient; diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index 82507f3815..4467260f96 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -464,7 +464,6 @@ public boolean hasExplicitIndexPermission( ) { final Set indicesForRequest = new HashSet<>(resolved.getAllIndicesResolved(cs, resolver)); - System.out.println("indicesForRequest: " + indicesForRequest); if (indicesForRequest.isEmpty()) { // If no indices could be found on the request there is no way to check for the explicit permissions return false; @@ -475,8 +474,6 @@ public boolean hasExplicitIndexPermission( .flatMap(Collection::stream) .collect(Collectors.toSet()); - System.out.println("explicitlyAllowedIndices: " + explicitlyAllowedIndices); - if (log.isDebugEnabled()) { log.debug( "ExplicitIndexPermission check indices for request {}, explicitly allowed indices {}", diff --git a/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLRequestHandler.java b/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLRequestHandler.java index 7002171595..758bd12132 100644 --- a/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLRequestHandler.java +++ b/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLRequestHandler.java @@ -47,7 +47,7 @@ public class SecuritySSLRequestHandler implements Tr private final String action; private final TransportRequestHandler actualHandler; - private final ThreadPool threadPool; + protected final ThreadPool threadPool; protected final Logger log = LogManager.getLogger(this.getClass()); private final PrincipalExtractor principalExtractor; private final SslExceptionHandler errorHandler; diff --git a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java index 5845c63672..8cff86a2dc 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,14 @@ 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); + // if (!deserializedUser.isPluginUser()) { + // getThreadContext().putPersistent( + // ConfigConstants.OPENDISTRO_SECURITY_SUBJECT, + // new SecurityUserSubject(threadPool, deserializedUser) + // ); + // } } String originalRemoteAddress = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER); From 77b728d062d5edd1154512406b2f4f4f2daebe6c Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 28 Aug 2024 09:23:11 -0400 Subject: [PATCH 23/49] Show use of default Signed-off-by: Craig Perkins --- .../java/org/opensearch/security/SystemIndexTests.java | 9 +++++++++ .../security/plugin/TransportRunClusterHealthAction.java | 8 +++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 304d48ea70..39a031b227 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -142,6 +142,15 @@ public void testAdminUserShouldBeAbleToRunClusterActions() { } } + @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)) { diff --git a/src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.java index 98264d6638..4be06933a2 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.java @@ -64,7 +64,7 @@ protected void doExecute(Task task, RunClusterHealthRequest request, ActionListe } catch (Exception e) { throw new RuntimeException(e); } - } else { + } else if ("plugin".equalsIgnoreCase(runAs)) { contextSwitcher.runAs(() -> { ActionListener chr = ActionListener.wrap( r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, @@ -73,6 +73,12 @@ protected void doExecute(Task task, RunClusterHealthRequest request, ActionListe 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); } } } From a64925781131ee5bfee97d20aba06561d381945c Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 28 Aug 2024 10:22:02 -0400 Subject: [PATCH 24/49] Show an example of switching subjects within a transport action Signed-off-by: Craig Perkins --- .../opensearch/security/SystemIndexTests.java | 10 +++++ .../IndexDocumentIntoSystemIndexRequest.java | 10 ++++- ...estIndexDocumentIntoSystemIndexAction.java | 3 +- ...ortIndexDocumentIntoSystemIndexAction.java | 39 ++++++++++++++----- .../security/OpenSearchSecurityPlugin.java | 1 - .../security/filter/SecurityFilter.java | 3 +- .../identity/SecurityUserSubject.java | 1 + 7 files changed, 53 insertions(+), 14 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 39a031b227..64266b22d9 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -118,6 +118,16 @@ public void testPluginShouldNotBeAbleToIndexDocumentIntoSystemIndexRegisteredByO } } + @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)) { diff --git a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java index 5cf7cf90d9..d1b644ad11 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java @@ -20,13 +20,17 @@ public class IndexDocumentIntoSystemIndexRequest extends ActionRequest { private final String indexName; - public IndexDocumentIntoSystemIndexRequest(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 @@ -37,4 +41,8 @@ public ActionRequestValidationException validate() { public String getIndexName() { return this.indexName; } + + public String getRunAs() { + return this.runAs; + } } diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java index a4a9fb3c6d..e668e8bccc 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestIndexDocumentIntoSystemIndexAction.java @@ -41,8 +41,9 @@ public String getName() { @Override public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) { + String runAs = request.param("runAs"); String indexName = request.param("index"); - IndexDocumentIntoSystemIndexRequest indexRequest = new IndexDocumentIntoSystemIndexRequest(indexName); + 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/TransportIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java index 93347a1515..c549da7809 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java @@ -19,6 +19,8 @@ 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; @@ -33,6 +35,7 @@ public class TransportIndexDocumentIntoSystemIndexAction extends HandledTranspor private final Client client; private final ThreadPool threadPool; private final PluginContextSwitcher contextSwitcher; + private final IdentityService identityService; @Inject public TransportIndexDocumentIntoSystemIndexAction( @@ -40,12 +43,14 @@ public TransportIndexDocumentIntoSystemIndexAction( final ActionFilters actionFilters, final Client client, final ThreadPool threadPool, - final PluginContextSwitcher contextSwitcher + 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 @@ -55,17 +60,33 @@ protected void doExecute( 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 -> { - 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) - ); + 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; }); diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 5f9ec458bb..a6286a6121 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -1053,7 +1053,6 @@ public Collection createComponents( } this.threadPool = threadPool; - System.out.println("ThreadPool createComponents: " + threadPool); this.cs = clusterService; this.localClient = localClient; diff --git a/src/main/java/org/opensearch/security/filter/SecurityFilter.java b/src/main/java/org/opensearch/security/filter/SecurityFilter.java index 96b015c830..ccfbc620c5 100644 --- a/src/main/java/org/opensearch/security/filter/SecurityFilter.java +++ b/src/main/java/org/opensearch/security/filter/SecurityFilter.java @@ -202,8 +202,6 @@ private void ap // However, if another plugin injected a user in the ThreadContext, we still need // to perform privileges checks. enforcePrivilegesEvaluation = true; - } else if (user != null && user.isPluginUser()) { - enforcePrivilegesEvaluation = true; } final boolean userIsAdmin = isUserAdmin(user, adminDns); final boolean interClusterRequest = HeaderHelper.isInterClusterRequest(threadContext); @@ -322,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/SecurityUserSubject.java b/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java index 4fe1732f86..76eb17b7cd 100644 --- a/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java +++ b/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java @@ -35,6 +35,7 @@ public Principal getPrincipal() { @Override public T runAs(Callable callable) throws Exception { try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) { + System.out.println("Switching to user: " + user); threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); return callable.call(); } From 7170c82e6bf86f48b140e3cfcb5125e6aff47696 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 28 Aug 2024 11:10:31 -0400 Subject: [PATCH 25/49] Show example of attaching subject to ActionRequest Signed-off-by: Craig Perkins --- .../IndexDocumentIntoSystemIndexRequest.java | 12 ++-- ...dexDocumentIntoMixOfSystemIndexAction.java | 10 ++-- ...ulkIndexDocumentIntoSystemIndexAction.java | 10 ++-- .../plugin/RestRunClusterHealthAction.java | 8 +-- .../plugin/RunClusterHealthRequest.java | 12 ++-- .../security/plugin/SystemIndexPlugin1.java | 18 +++--- ...ortIndexDocumentIntoSystemIndexAction.java | 58 +++++++++---------- .../TransportRunClusterHealthAction.java | 12 ++-- ...Switcher.java => PluginSubjectHolder.java} | 8 ++- 9 files changed, 73 insertions(+), 75 deletions(-) rename src/main/java/org/opensearch/security/identity/{PluginContextSwitcher.java => PluginSubjectHolder.java} (84%) diff --git a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java index d1b644ad11..4847ed0421 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java @@ -20,17 +20,17 @@ public class IndexDocumentIntoSystemIndexRequest extends ActionRequest { private final String indexName; - private final String runAs; + private final String runActionAs; - public IndexDocumentIntoSystemIndexRequest(String indexName, String runAs) { + public IndexDocumentIntoSystemIndexRequest(String indexName, String runActionAs) { this.indexName = indexName; - this.runAs = runAs; + this.runActionAs = runActionAs; } public IndexDocumentIntoSystemIndexRequest(StreamInput in) throws IOException { super(in); this.indexName = in.readString(); - this.runAs = in.readOptionalString(); + this.runActionAs = in.readOptionalString(); } @Override @@ -42,7 +42,7 @@ public String getIndexName() { return this.indexName; } - public String getRunAs() { - return this.runAs; + public String getRunActionAs() { + return this.runActionAs; } } diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java index 0d1dc4fe01..cdddea95be 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java @@ -26,7 +26,7 @@ import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; -import org.opensearch.security.identity.PluginContextSwitcher; +import org.opensearch.security.identity.PluginSubjectHolder; import static java.util.Collections.singletonList; import static org.opensearch.rest.RestRequest.Method.PUT; @@ -36,11 +36,11 @@ public class RestBulkIndexDocumentIntoMixOfSystemIndexAction extends BaseRestHandler { private final Client client; - private final PluginContextSwitcher contextSwitcher; + private final PluginSubjectHolder pluginSubjectHolder; - public RestBulkIndexDocumentIntoMixOfSystemIndexAction(Client client, PluginContextSwitcher contextSwitcher) { + public RestBulkIndexDocumentIntoMixOfSystemIndexAction(Client client, PluginSubjectHolder pluginSubjectHolder) { this.client = client; - this.contextSwitcher = contextSwitcher; + this.pluginSubjectHolder = pluginSubjectHolder; } @Override @@ -59,7 +59,7 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client @Override public void accept(RestChannel channel) throws Exception { - contextSwitcher.runAs(() -> { + pluginSubjectHolder.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)); diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java index 56fa3ef2a9..c730d79fc4 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java @@ -27,7 +27,7 @@ import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; -import org.opensearch.security.identity.PluginContextSwitcher; +import org.opensearch.security.identity.PluginSubjectHolder; import static java.util.Collections.singletonList; import static org.opensearch.rest.RestRequest.Method.PUT; @@ -35,11 +35,11 @@ public class RestBulkIndexDocumentIntoSystemIndexAction extends BaseRestHandler { private final Client client; - private final PluginContextSwitcher contextSwitcher; + private final PluginSubjectHolder pluginSubjectHolder; - public RestBulkIndexDocumentIntoSystemIndexAction(Client client, PluginContextSwitcher contextSwitcher) { + public RestBulkIndexDocumentIntoSystemIndexAction(Client client, PluginSubjectHolder pluginSubjectHolder) { this.client = client; - this.contextSwitcher = contextSwitcher; + this.pluginSubjectHolder = pluginSubjectHolder; } @Override @@ -59,7 +59,7 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client @Override public void accept(RestChannel channel) throws Exception { - contextSwitcher.runAs(() -> { + pluginSubjectHolder.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)); diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java index 755f3278f0..3a5e1cc407 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java @@ -17,7 +17,7 @@ import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.RestRequest; import org.opensearch.rest.action.RestToXContentListener; -import org.opensearch.security.identity.PluginContextSwitcher; +import org.opensearch.security.identity.PluginSubjectHolder; import static java.util.Collections.singletonList; import static org.opensearch.rest.RestRequest.Method.GET; @@ -25,11 +25,11 @@ public class RestRunClusterHealthAction extends BaseRestHandler { private final Client client; - private final PluginContextSwitcher contextSwitcher; + private final PluginSubjectHolder pluginSubjectHolder; - public RestRunClusterHealthAction(Client client, PluginContextSwitcher contextSwitcher) { + public RestRunClusterHealthAction(Client client, PluginSubjectHolder pluginSubjectHolder) { this.client = client; - this.contextSwitcher = contextSwitcher; + this.pluginSubjectHolder = pluginSubjectHolder; } @Override diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthRequest.java b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthRequest.java index 8ae08bd6ff..383847f2fa 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthRequest.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthRequest.java @@ -18,15 +18,15 @@ public class RunClusterHealthRequest extends ActionRequest { - private final String runAs; + private final String runActionAs; - public RunClusterHealthRequest(String runAs) { - this.runAs = runAs; + public RunClusterHealthRequest(String runActionAs) { + this.runActionAs = runActionAs; } public RunClusterHealthRequest(StreamInput in) throws IOException { super(in); - this.runAs = in.readString(); + this.runActionAs = in.readString(); } @Override @@ -34,7 +34,7 @@ public ActionRequestValidationException validate() { return null; } - public String getRunAs() { - return this.runAs; + public String getRunActionAs() { + return runActionAs; } } diff --git a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java index 3f1112c4b6..fc3b0fde93 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -39,14 +39,14 @@ import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; import org.opensearch.script.ScriptService; -import org.opensearch.security.identity.PluginContextSwitcher; +import org.opensearch.security.identity.PluginSubjectHolder; 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 PluginSubjectHolder pluginSubjectHolder; private Client client; @@ -65,8 +65,8 @@ public Collection createComponents( Supplier repositoriesServiceSupplier ) { this.client = client; - this.contextSwitcher = new PluginContextSwitcher(); - return List.of(contextSwitcher); + this.pluginSubjectHolder = new PluginSubjectHolder(); + return List.of(pluginSubjectHolder); } @Override @@ -87,9 +87,9 @@ public List getRestHandlers( ) { return List.of( new RestIndexDocumentIntoSystemIndexAction(client), - new RestRunClusterHealthAction(client, contextSwitcher), - new RestBulkIndexDocumentIntoSystemIndexAction(client, contextSwitcher), - new RestBulkIndexDocumentIntoMixOfSystemIndexAction(client, contextSwitcher) + new RestRunClusterHealthAction(client, pluginSubjectHolder), + new RestBulkIndexDocumentIntoSystemIndexAction(client, pluginSubjectHolder), + new RestBulkIndexDocumentIntoMixOfSystemIndexAction(client, pluginSubjectHolder) ); } @@ -103,8 +103,8 @@ public List getRestHandlers( @Override public void assignSubject(PluginSubject pluginSystemSubject) { - if (contextSwitcher != null) { - this.contextSwitcher.initialize(pluginSystemSubject); + if (pluginSubjectHolder != null) { + this.pluginSubjectHolder.initialize(pluginSystemSubject); } } } diff --git a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java index c549da7809..9efd32a988 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java @@ -21,7 +21,7 @@ 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.identity.PluginSubjectHolder; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.user.User; import org.opensearch.tasks.Task; @@ -34,7 +34,7 @@ public class TransportIndexDocumentIntoSystemIndexAction extends HandledTranspor private final Client client; private final ThreadPool threadPool; - private final PluginContextSwitcher contextSwitcher; + private final PluginSubjectHolder pluginSubjectHolder; private final IdentityService identityService; @Inject @@ -43,13 +43,13 @@ public TransportIndexDocumentIntoSystemIndexAction( final ActionFilters actionFilters, final Client client, final ThreadPool threadPool, - final PluginContextSwitcher contextSwitcher, + final PluginSubjectHolder pluginSubjectHolder, final IdentityService identityService ) { super(IndexDocumentIntoSystemIndexAction.NAME, transportService, actionFilters, IndexDocumentIntoSystemIndexRequest::new); this.client = client; this.threadPool = threadPool; - this.contextSwitcher = contextSwitcher; + this.pluginSubjectHolder = pluginSubjectHolder; this.identityService = identityService; } @@ -60,36 +60,30 @@ protected void doExecute( ActionListener actionListener ) { String indexName = request.getIndexName(); - String runAs = request.getRunAs(); + String runAs = request.getRunActionAs(); 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; - }); + CreateIndexRequest cir = new CreateIndexRequest(indexName); + cir.runAs(pluginSubjectHolder.getPluginSubject()); + client.admin().indices().create(cir, ActionListener.wrap(r -> { + if ("user".equalsIgnoreCase(runAs)) { + IndexRequest ir = new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON); + ir.runAs(userSubject); + client.index(ir, ActionListener.wrap(r2 -> { + User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, user.getName())); + }, actionListener::onFailure)); + } else { + IndexRequest ir = new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("{\"content\":1}", XContentType.JSON); + ir.runAs(pluginSubjectHolder.getPluginSubject()); + client.index(ir, ActionListener.wrap(r2 -> { + User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, user.getName())); + }, actionListener::onFailure)); + } + }, actionListener::onFailure)); } 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 index 4be06933a2..89c466a8b9 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.java @@ -19,7 +19,7 @@ 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.identity.PluginSubjectHolder; import org.opensearch.tasks.Task; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.TransportService; @@ -28,7 +28,7 @@ public class TransportRunClusterHealthAction extends HandledTransportAction actionListener) { - String runAs = request.getRunAs(); + String runAs = request.getRunActionAs(); if ("user".equalsIgnoreCase(runAs)) { Subject user = identityService.getCurrentSubject(); try { @@ -65,7 +65,7 @@ protected void doExecute(Task task, RunClusterHealthRequest request, ActionListe throw new RuntimeException(e); } } else if ("plugin".equalsIgnoreCase(runAs)) { - contextSwitcher.runAs(() -> { + pluginSubjectHolder.runAs(() -> { ActionListener chr = ActionListener.wrap( r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, actionListener::onFailure diff --git a/src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java b/src/main/java/org/opensearch/security/identity/PluginSubjectHolder.java similarity index 84% rename from src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java rename to src/main/java/org/opensearch/security/identity/PluginSubjectHolder.java index 52809d4fab..200c741eb6 100644 --- a/src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java +++ b/src/main/java/org/opensearch/security/identity/PluginSubjectHolder.java @@ -14,10 +14,10 @@ import org.opensearch.identity.PluginSubject; -public class PluginContextSwitcher { +public class PluginSubjectHolder { private PluginSubject pluginSubject; - public PluginContextSwitcher() {} + public PluginSubjectHolder() {} public void initialize(PluginSubject pluginSubject) { this.pluginSubject = pluginSubject; @@ -33,4 +33,8 @@ public T runAs(Callable callable) { throw new RuntimeException(e); } } + + public PluginSubject getPluginSubject() { + return this.pluginSubject; + } } From 9c205a6d9e8413417dc29ce40cd07cfdbab4f0a6 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Wed, 28 Aug 2024 11:59:09 -0400 Subject: [PATCH 26/49] Revert "Show example of attaching subject to ActionRequest" This reverts commit 7170c82e6bf86f48b140e3cfcb5125e6aff47696. Signed-off-by: Craig Perkins --- .../IndexDocumentIntoSystemIndexRequest.java | 12 ++-- ...dexDocumentIntoMixOfSystemIndexAction.java | 10 ++-- ...ulkIndexDocumentIntoSystemIndexAction.java | 10 ++-- .../plugin/RestRunClusterHealthAction.java | 8 +-- .../plugin/RunClusterHealthRequest.java | 12 ++-- .../security/plugin/SystemIndexPlugin1.java | 18 +++--- ...ortIndexDocumentIntoSystemIndexAction.java | 58 ++++++++++--------- .../TransportRunClusterHealthAction.java | 12 ++-- ...Holder.java => PluginContextSwitcher.java} | 8 +-- 9 files changed, 75 insertions(+), 73 deletions(-) rename src/main/java/org/opensearch/security/identity/{PluginSubjectHolder.java => PluginContextSwitcher.java} (84%) diff --git a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java index 4847ed0421..d1b644ad11 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexRequest.java @@ -20,17 +20,17 @@ public class IndexDocumentIntoSystemIndexRequest extends ActionRequest { private final String indexName; - private final String runActionAs; + private final String runAs; - public IndexDocumentIntoSystemIndexRequest(String indexName, String runActionAs) { + public IndexDocumentIntoSystemIndexRequest(String indexName, String runAs) { this.indexName = indexName; - this.runActionAs = runActionAs; + this.runAs = runAs; } public IndexDocumentIntoSystemIndexRequest(StreamInput in) throws IOException { super(in); this.indexName = in.readString(); - this.runActionAs = in.readOptionalString(); + this.runAs = in.readOptionalString(); } @Override @@ -42,7 +42,7 @@ public String getIndexName() { return this.indexName; } - public String getRunActionAs() { - return this.runActionAs; + public String getRunAs() { + return this.runAs; } } diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java index cdddea95be..0d1dc4fe01 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoMixOfSystemIndexAction.java @@ -26,7 +26,7 @@ import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; -import org.opensearch.security.identity.PluginSubjectHolder; +import org.opensearch.security.identity.PluginContextSwitcher; import static java.util.Collections.singletonList; import static org.opensearch.rest.RestRequest.Method.PUT; @@ -36,11 +36,11 @@ public class RestBulkIndexDocumentIntoMixOfSystemIndexAction extends BaseRestHandler { private final Client client; - private final PluginSubjectHolder pluginSubjectHolder; + private final PluginContextSwitcher contextSwitcher; - public RestBulkIndexDocumentIntoMixOfSystemIndexAction(Client client, PluginSubjectHolder pluginSubjectHolder) { + public RestBulkIndexDocumentIntoMixOfSystemIndexAction(Client client, PluginContextSwitcher contextSwitcher) { this.client = client; - this.pluginSubjectHolder = pluginSubjectHolder; + this.contextSwitcher = contextSwitcher; } @Override @@ -59,7 +59,7 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client @Override public void accept(RestChannel channel) throws Exception { - pluginSubjectHolder.runAs(() -> { + 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)); diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java index c730d79fc4..56fa3ef2a9 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestBulkIndexDocumentIntoSystemIndexAction.java @@ -27,7 +27,7 @@ import org.opensearch.rest.BytesRestResponse; import org.opensearch.rest.RestChannel; import org.opensearch.rest.RestRequest; -import org.opensearch.security.identity.PluginSubjectHolder; +import org.opensearch.security.identity.PluginContextSwitcher; import static java.util.Collections.singletonList; import static org.opensearch.rest.RestRequest.Method.PUT; @@ -35,11 +35,11 @@ public class RestBulkIndexDocumentIntoSystemIndexAction extends BaseRestHandler { private final Client client; - private final PluginSubjectHolder pluginSubjectHolder; + private final PluginContextSwitcher contextSwitcher; - public RestBulkIndexDocumentIntoSystemIndexAction(Client client, PluginSubjectHolder pluginSubjectHolder) { + public RestBulkIndexDocumentIntoSystemIndexAction(Client client, PluginContextSwitcher contextSwitcher) { this.client = client; - this.pluginSubjectHolder = pluginSubjectHolder; + this.contextSwitcher = contextSwitcher; } @Override @@ -59,7 +59,7 @@ public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client @Override public void accept(RestChannel channel) throws Exception { - pluginSubjectHolder.runAs(() -> { + 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)); diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java index 3a5e1cc407..755f3278f0 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RestRunClusterHealthAction.java @@ -17,7 +17,7 @@ import org.opensearch.rest.BaseRestHandler; import org.opensearch.rest.RestRequest; import org.opensearch.rest.action.RestToXContentListener; -import org.opensearch.security.identity.PluginSubjectHolder; +import org.opensearch.security.identity.PluginContextSwitcher; import static java.util.Collections.singletonList; import static org.opensearch.rest.RestRequest.Method.GET; @@ -25,11 +25,11 @@ public class RestRunClusterHealthAction extends BaseRestHandler { private final Client client; - private final PluginSubjectHolder pluginSubjectHolder; + private final PluginContextSwitcher contextSwitcher; - public RestRunClusterHealthAction(Client client, PluginSubjectHolder pluginSubjectHolder) { + public RestRunClusterHealthAction(Client client, PluginContextSwitcher contextSwitcher) { this.client = client; - this.pluginSubjectHolder = pluginSubjectHolder; + this.contextSwitcher = contextSwitcher; } @Override diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthRequest.java b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthRequest.java index 383847f2fa..8ae08bd6ff 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthRequest.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthRequest.java @@ -18,15 +18,15 @@ public class RunClusterHealthRequest extends ActionRequest { - private final String runActionAs; + private final String runAs; - public RunClusterHealthRequest(String runActionAs) { - this.runActionAs = runActionAs; + public RunClusterHealthRequest(String runAs) { + this.runAs = runAs; } public RunClusterHealthRequest(StreamInput in) throws IOException { super(in); - this.runActionAs = in.readString(); + this.runAs = in.readString(); } @Override @@ -34,7 +34,7 @@ public ActionRequestValidationException validate() { return null; } - public String getRunActionAs() { - return runActionAs; + public String getRunAs() { + return this.runAs; } } diff --git a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java index fc3b0fde93..3f1112c4b6 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/SystemIndexPlugin1.java @@ -39,14 +39,14 @@ import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; import org.opensearch.script.ScriptService; -import org.opensearch.security.identity.PluginSubjectHolder; +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 PluginSubjectHolder pluginSubjectHolder; + private PluginContextSwitcher contextSwitcher; private Client client; @@ -65,8 +65,8 @@ public Collection createComponents( Supplier repositoriesServiceSupplier ) { this.client = client; - this.pluginSubjectHolder = new PluginSubjectHolder(); - return List.of(pluginSubjectHolder); + this.contextSwitcher = new PluginContextSwitcher(); + return List.of(contextSwitcher); } @Override @@ -87,9 +87,9 @@ public List getRestHandlers( ) { return List.of( new RestIndexDocumentIntoSystemIndexAction(client), - new RestRunClusterHealthAction(client, pluginSubjectHolder), - new RestBulkIndexDocumentIntoSystemIndexAction(client, pluginSubjectHolder), - new RestBulkIndexDocumentIntoMixOfSystemIndexAction(client, pluginSubjectHolder) + new RestRunClusterHealthAction(client, contextSwitcher), + new RestBulkIndexDocumentIntoSystemIndexAction(client, contextSwitcher), + new RestBulkIndexDocumentIntoMixOfSystemIndexAction(client, contextSwitcher) ); } @@ -103,8 +103,8 @@ public List getRestHandlers( @Override public void assignSubject(PluginSubject pluginSystemSubject) { - if (pluginSubjectHolder != null) { - this.pluginSubjectHolder.initialize(pluginSystemSubject); + if (contextSwitcher != null) { + this.contextSwitcher.initialize(pluginSystemSubject); } } } diff --git a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java index 9efd32a988..c549da7809 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportIndexDocumentIntoSystemIndexAction.java @@ -21,7 +21,7 @@ import org.opensearch.core.action.ActionListener; import org.opensearch.identity.IdentityService; import org.opensearch.identity.Subject; -import org.opensearch.security.identity.PluginSubjectHolder; +import org.opensearch.security.identity.PluginContextSwitcher; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.user.User; import org.opensearch.tasks.Task; @@ -34,7 +34,7 @@ public class TransportIndexDocumentIntoSystemIndexAction extends HandledTranspor private final Client client; private final ThreadPool threadPool; - private final PluginSubjectHolder pluginSubjectHolder; + private final PluginContextSwitcher contextSwitcher; private final IdentityService identityService; @Inject @@ -43,13 +43,13 @@ public TransportIndexDocumentIntoSystemIndexAction( final ActionFilters actionFilters, final Client client, final ThreadPool threadPool, - final PluginSubjectHolder pluginSubjectHolder, + final PluginContextSwitcher contextSwitcher, final IdentityService identityService ) { super(IndexDocumentIntoSystemIndexAction.NAME, transportService, actionFilters, IndexDocumentIntoSystemIndexRequest::new); this.client = client; this.threadPool = threadPool; - this.pluginSubjectHolder = pluginSubjectHolder; + this.contextSwitcher = contextSwitcher; this.identityService = identityService; } @@ -60,30 +60,36 @@ protected void doExecute( ActionListener actionListener ) { String indexName = request.getIndexName(); - String runAs = request.getRunActionAs(); + String runAs = request.getRunAs(); Subject userSubject = identityService.getCurrentSubject(); try { - CreateIndexRequest cir = new CreateIndexRequest(indexName); - cir.runAs(pluginSubjectHolder.getPluginSubject()); - client.admin().indices().create(cir, ActionListener.wrap(r -> { - if ("user".equalsIgnoreCase(runAs)) { - IndexRequest ir = new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) - .source("{\"content\":1}", XContentType.JSON); - ir.runAs(userSubject); - client.index(ir, ActionListener.wrap(r2 -> { - User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, user.getName())); - }, actionListener::onFailure)); - } else { - IndexRequest ir = new IndexRequest(indexName).setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) - .source("{\"content\":1}", XContentType.JSON); - ir.runAs(pluginSubjectHolder.getPluginSubject()); - client.index(ir, ActionListener.wrap(r2 -> { - User user = threadPool.getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - actionListener.onResponse(new IndexDocumentIntoSystemIndexResponse(true, user.getName())); - }, actionListener::onFailure)); - } - }, actionListener::onFailure)); + 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 index 89c466a8b9..4be06933a2 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/TransportRunClusterHealthAction.java @@ -19,7 +19,7 @@ import org.opensearch.core.action.ActionListener; import org.opensearch.identity.IdentityService; import org.opensearch.identity.Subject; -import org.opensearch.security.identity.PluginSubjectHolder; +import org.opensearch.security.identity.PluginContextSwitcher; import org.opensearch.tasks.Task; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.TransportService; @@ -28,7 +28,7 @@ public class TransportRunClusterHealthAction extends HandledTransportAction actionListener) { - String runAs = request.getRunActionAs(); + String runAs = request.getRunAs(); if ("user".equalsIgnoreCase(runAs)) { Subject user = identityService.getCurrentSubject(); try { @@ -65,7 +65,7 @@ protected void doExecute(Task task, RunClusterHealthRequest request, ActionListe throw new RuntimeException(e); } } else if ("plugin".equalsIgnoreCase(runAs)) { - pluginSubjectHolder.runAs(() -> { + contextSwitcher.runAs(() -> { ActionListener chr = ActionListener.wrap( r -> { actionListener.onResponse(new RunClusterHealthResponse(true)); }, actionListener::onFailure diff --git a/src/main/java/org/opensearch/security/identity/PluginSubjectHolder.java b/src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java similarity index 84% rename from src/main/java/org/opensearch/security/identity/PluginSubjectHolder.java rename to src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java index 200c741eb6..52809d4fab 100644 --- a/src/main/java/org/opensearch/security/identity/PluginSubjectHolder.java +++ b/src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java @@ -14,10 +14,10 @@ import org.opensearch.identity.PluginSubject; -public class PluginSubjectHolder { +public class PluginContextSwitcher { private PluginSubject pluginSubject; - public PluginSubjectHolder() {} + public PluginContextSwitcher() {} public void initialize(PluginSubject pluginSubject) { this.pluginSubject = pluginSubject; @@ -33,8 +33,4 @@ public T runAs(Callable callable) { throw new RuntimeException(e); } } - - public PluginSubject getPluginSubject() { - return this.pluginSubject; - } } From 690144e75bce2f8b5064e23a277e6ed40c726a09 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 29 Aug 2024 12:11:16 -0400 Subject: [PATCH 27/49] Remove copy of SystemIndexRegistry and use exposed methods from core Signed-off-by: Craig Perkins --- .../security/OpenSearchSecurityPlugin.java | 16 +----------- .../identity/SystemIndexRegistry.java | 21 --------------- .../privileges/PrivilegesEvaluator.java | 10 ++----- .../SystemIndexAccessEvaluator.java | 26 ++++++++++++++++++- .../security/securityconf/ConfigModelV6.java | 9 +------ .../security/securityconf/ConfigModelV7.java | 8 +----- .../security/securityconf/SecurityRoles.java | 8 +----- 7 files changed, 31 insertions(+), 67 deletions(-) delete mode 100644 src/main/java/org/opensearch/security/identity/SystemIndexRegistry.java diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index a6286a6121..9fde77ff87 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -123,7 +123,6 @@ import org.opensearch.plugins.SecureHttpTransportSettingsProvider; import org.opensearch.plugins.SecureSettingsFactory; import org.opensearch.plugins.SecureTransportSettingsProvider; -import org.opensearch.plugins.SystemIndexPlugin; import org.opensearch.repositories.RepositoriesService; import org.opensearch.rest.RestController; import org.opensearch.rest.RestHandler; @@ -168,7 +167,6 @@ import org.opensearch.security.http.XFFResolver; import org.opensearch.security.identity.ContextProvidingPluginSubject; import org.opensearch.security.identity.SecurityTokenManager; -import org.opensearch.security.identity.SystemIndexRegistry; import org.opensearch.security.privileges.PrivilegesEvaluator; import org.opensearch.security.privileges.PrivilegesInterceptor; import org.opensearch.security.privileges.RestLayerPrivilegesEvaluator; @@ -270,7 +268,6 @@ public final class OpenSearchSecurityPlugin extends OpenSearchSecuritySSLPlugin private volatile Salt salt; private volatile OpensearchDynamicSetting transportPassiveAuthSetting; private volatile PasswordHasher passwordHasher; - private volatile SystemIndexRegistry systemIndexRegistry; public static boolean isActionTraceEnabled() { @@ -1120,8 +1117,6 @@ public Collection createComponents( final CompatConfig compatConfig = new CompatConfig(environment, transportPassiveAuthSetting); - systemIndexRegistry = new SystemIndexRegistry(); - evaluator = new PrivilegesEvaluator( clusterService, threadPool, @@ -1132,8 +1127,7 @@ public Collection createComponents( privilegesInterceptor, cih, irr, - namedXContentRegistry.get(), - systemIndexRegistry + namedXContentRegistry.get() ); sf = new SecurityFilter(settings, evaluator, adminDns, dlsFlsValve, auditLog, threadPool, cs, compatConfig, irr, xffResolver); @@ -1212,7 +1206,6 @@ public Collection createComponents( components.add(dcf); components.add(userService); components.add(passwordHasher); - components.add(systemIndexRegistry); if (!ExternalSecurityKeyStore.hasExternalSslContext(settings)) { components.add(sks); @@ -2123,13 +2116,6 @@ public SecurityTokenManager getTokenManager() { @Override public PluginSubject getPluginSubject(Plugin plugin) { - if (systemIndexRegistry != null) { - Collection systemIndexDescriptors = ((SystemIndexPlugin) plugin).getSystemIndexDescriptors(settings); - Set systemIndexPatterns = systemIndexDescriptors.stream() - .map(SystemIndexDescriptor::getIndexPattern) - .collect(Collectors.toSet()); - systemIndexRegistry.addSystemIntexPatterns(plugin.getClass().getCanonicalName(), systemIndexPatterns); - } return new ContextProvidingPluginSubject(threadPool, settings, plugin); } diff --git a/src/main/java/org/opensearch/security/identity/SystemIndexRegistry.java b/src/main/java/org/opensearch/security/identity/SystemIndexRegistry.java deleted file mode 100644 index 8fbb34dfc0..0000000000 --- a/src/main/java/org/opensearch/security/identity/SystemIndexRegistry.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.opensearch.security.identity; - -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -public class SystemIndexRegistry { - private final Map> registeredSystemIndexPatterns; - - public SystemIndexRegistry() { - registeredSystemIndexPatterns = new HashMap<>(); - } - - public void addSystemIntexPatterns(String pluginIdentifier, Set indexPatterns) { - registeredSystemIndexPatterns.put(pluginIdentifier, indexPatterns); - } - - public Set getSystemIndexPatterns(String pluginIdentifier) { - return registeredSystemIndexPatterns.get(pluginIdentifier); - } -} diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 7739603139..903c5c2046 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -86,7 +86,6 @@ import org.opensearch.security.auditlog.AuditLog; import org.opensearch.security.configuration.ClusterInfoHolder; import org.opensearch.security.configuration.ConfigurationRepository; -import org.opensearch.security.identity.SystemIndexRegistry; import org.opensearch.security.resolver.IndexResolverReplacer; import org.opensearch.security.resolver.IndexResolverReplacer.Resolved; import org.opensearch.security.securityconf.ConfigModel; @@ -144,7 +143,6 @@ public class PrivilegesEvaluator { private final PitPrivilegesEvaluator pitPrivilegesEvaluator; private DynamicConfigModel dcm; private final NamedXContentRegistry namedXContentRegistry; - private final SystemIndexRegistry systemIndexRegistry; private final Map pluginRoles; public PrivilegesEvaluator( @@ -157,8 +155,7 @@ public PrivilegesEvaluator( final PrivilegesInterceptor privilegesInterceptor, final ClusterInfoHolder clusterInfoHolder, final IndexResolverReplacer irr, - NamedXContentRegistry namedXContentRegistry, - final SystemIndexRegistry systemIndexRegistry + NamedXContentRegistry namedXContentRegistry ) { super(); @@ -168,7 +165,6 @@ public PrivilegesEvaluator( this.threadContext = threadPool.getThreadContext(); this.privilegesInterceptor = privilegesInterceptor; - this.systemIndexRegistry = systemIndexRegistry; this.pluginRoles = new HashMap<>(); this.checkSnapshotRestoreWritePrivileges = settings.getAsBoolean( @@ -203,9 +199,7 @@ public SecurityRoles getSecurityRoles(Set roles) { public SecurityRoles getSecurityRoleForPlugin(String pluginIdentifier) { SecurityRoles pluginRole = pluginRoles.get(pluginIdentifier); if (pluginRole == null) { - Set systemIndexPatterns = systemIndexRegistry.getSystemIndexPatterns(pluginIdentifier); - pluginRole = configModel.getSecurityRoles() - .createSecurityRole(pluginIdentifier, Set.of(BulkAction.NAME), Set.of(), Set.of(), systemIndexPatterns); + pluginRole = configModel.getSecurityRoles().createSecurityRole(pluginIdentifier, Set.of(BulkAction.NAME), Set.of(), Set.of()); pluginRoles.put(pluginIdentifier, pluginRole); } return pluginRole; diff --git a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java index affe6177b5..61c487357e 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -277,6 +277,31 @@ private void evaluateSystemIndicesAccess( return; } boolean containsProtectedIndex = requestContainsAnyProtectedSystemIndices(requestedResolved); + if (user.isPluginUser()) { + Set matchingSystemIndices = SystemIndexRegistry.matchesPluginSystemIndexPattern( + user.getName(), + 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.missingPrivileges.add(action); + presponse.markComplete(); + } + return; + } + if (containsProtectedIndex) { auditLog.logSecurityIndexAttempt(request, action, task); if (log.isInfoEnabled()) { @@ -307,7 +332,6 @@ private void evaluateSystemIndicesAccess( String.join(", ", getAllSystemIndices(requestedResolved)) ); } - System.out.println("Not authorized"); presponse.allowed = false; presponse.missingPrivileges.add(action); presponse.markComplete(); diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java index 6157c9deef..dd296931c5 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java @@ -364,8 +364,7 @@ public SecurityRoles createSecurityRole( String roleName, Set clusterPerms, Set indexPatterns, - Set allowedActions, - Set systemIndexPatterns + Set allowedActions ) { SecurityRole role = new SecurityRole(roleName); role.addClusterPerms(clusterPerms); @@ -375,12 +374,6 @@ public SecurityRoles createSecurityRole( perms.addPerms(allowedActions); idxPattern.addTypePerms(perms); } - for (String ip : systemIndexPatterns) { - IndexPattern idxPattern = new IndexPattern(ip); - TypePerm perms = new TypePerm(""); - perms.addPerms(Set.of("*", ConfigConstants.SYSTEM_INDEX_PERMISSION)); - idxPattern.addTypePerms(perms); - } SecurityRoles roles = new SecurityRoles(1); roles.addSecurityRole(role); return roles; diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index 4467260f96..263ff0b459 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -278,8 +278,7 @@ public SecurityRoles createSecurityRole( String roleName, Set clusterPerms, Set indexPatterns, - Set allowedActions, - Set systemIndexPatterns + Set allowedActions ) { Set ipatterns = new HashSet<>(); for (String ip : indexPatterns) { @@ -287,11 +286,6 @@ public SecurityRoles createSecurityRole( idxPattern.addPerm(allowedActions); ipatterns.add(idxPattern); } - for (String ip : systemIndexPatterns) { - IndexPattern idxPattern = new IndexPattern(ip); - idxPattern.addPerm(Set.of("*", ConfigConstants.SYSTEM_INDEX_PERMISSION)); - ipatterns.add(idxPattern); - } SecurityRole role = new SecurityRole(roleName, ipatterns, WildcardMatcher.from(clusterPerms)); SecurityRoles roles = new SecurityRoles(1); roles.addSecurityRole(role); diff --git a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java index 9068b37aa6..8c03188c8d 100644 --- a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java +++ b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java @@ -97,13 +97,7 @@ Set getAllPermittedIndicesForDashboards( SecurityRoles filter(Set roles); - SecurityRoles createSecurityRole( - String roleName, - Set clusterPerms, - Set indexPatterns, - Set allowedActions, - Set systemIndexPatterns - ); + SecurityRoles createSecurityRole(String roleName, Set clusterPerms, Set indexPatterns, Set allowedActions); boolean isPermittedOnSystemIndex(String indexName); } From e781e370a82006091446e66978a71a2d39e203f6 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Thu, 29 Aug 2024 13:30:04 -0400 Subject: [PATCH 28/49] Fix failing tests Signed-off-by: Craig Perkins --- .../security/privileges/PrivilegesEvaluatorResponse.java | 4 ++++ .../security/privileges/SystemIndexAccessEvaluator.java | 2 +- .../security/privileges/SystemIndexAccessEvaluatorTest.java | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) 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/SystemIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java index 61c487357e..1eca349e62 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -333,7 +333,7 @@ private void evaluateSystemIndicesAccess( ); } presponse.allowed = false; - presponse.missingPrivileges.add(action); + presponse.addMissingPrivileges(action); presponse.markComplete(); return; } diff --git a/src/test/java/org/opensearch/security/privileges/SystemIndexAccessEvaluatorTest.java b/src/test/java/org/opensearch/security/privileges/SystemIndexAccessEvaluatorTest.java index fa8a991db9..ee99a05704 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); } @@ -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 @@ -598,6 +603,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 From 3c608087c800d36b38dd35a0b095f072da3b88b8 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 30 Aug 2024 17:23:28 -0400 Subject: [PATCH 29/49] Fix SystemIndexPermissionEnabledTests tests Signed-off-by: Craig Perkins --- .../privileges/SnapshotRestoreEvaluator.java | 1 + .../SystemIndexAccessEvaluator.java | 8 ++- .../AbstractSystemIndicesTests.java | 3 +- .../SystemIndexPermissionEnabledTests.java | 50 ++++++++++--------- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java b/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java index 23612e1a52..b206442585 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.missingPrivileges.add(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 1eca349e62..89adab64e8 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -272,7 +272,7 @@ private void evaluateSystemIndicesAccess( log.debug("Service account cannot access regular indices: {}", regularIndices); } presponse.allowed = false; - presponse.missingPrivileges.add(action); + presponse.addMissingPrivileges(action); presponse.markComplete(); return; } @@ -296,7 +296,7 @@ private void evaluateSystemIndicesAccess( ); } presponse.allowed = false; - presponse.missingPrivileges.add(action); + presponse.addMissingPrivileges(action); presponse.markComplete(); } return; @@ -313,6 +313,7 @@ private void evaluateSystemIndicesAccess( ); } presponse.allowed = false; + presponse.addMissingPrivileges(action); presponse.markComplete(); return; } else if (containsSystemIndex @@ -356,6 +357,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(); } } @@ -370,6 +372,7 @@ else if (containsSystemIndex && !isSystemIndexPermissionEnabled) { log.debug("Filtered '{}' but resulting list is empty", securityIndex); } presponse.allowed = false; + presponse.addMissingPrivileges(action); presponse.markComplete(); return; } @@ -382,6 +385,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/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/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); } } From e42a4a0e76b47667c37bfda33161466b6ffe5fae Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 30 Aug 2024 20:41:28 -0400 Subject: [PATCH 30/49] Fix tests Signed-off-by: Craig Perkins --- .../privileges/SystemIndexAccessEvaluatorTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/test/java/org/opensearch/security/privileges/SystemIndexAccessEvaluatorTest.java b/src/test/java/org/opensearch/security/privileges/SystemIndexAccessEvaluatorTest.java index ee99a05704..4dc5c16560 100644 --- a/src/test/java/org/opensearch/security/privileges/SystemIndexAccessEvaluatorTest.java +++ b/src/test/java/org/opensearch/security/privileges/SystemIndexAccessEvaluatorTest.java @@ -500,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"); } @@ -514,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); } @@ -528,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); } @@ -586,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); } @@ -628,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); @@ -643,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); @@ -678,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(); From 41e83b18ec8883107998762c38e69f5f627f1095 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 30 Aug 2024 21:26:31 -0400 Subject: [PATCH 31/49] Fix disabled tests Signed-off-by: Craig Perkins --- .../SystemIndexPermissionDisabledTests.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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); } From c9f1ff7d7cf07da4258a328e6946ec20169da762 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 30 Aug 2024 22:00:03 -0400 Subject: [PATCH 32/49] Fix more tests Signed-off-by: Craig Perkins --- .../system_indices/SystemIndexDisabledTests.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) 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); From 0a696ff4321f4bbca153bebf6dbc4601aee38db2 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 3 Sep 2024 09:42:03 -0400 Subject: [PATCH 33/49] Add license headers Signed-off-by: Craig Perkins --- .../security/identity/ContextProvidingPluginSubject.java | 9 +++++++++ .../security/identity/SecurityUserSubject.java | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java index 63ed349ca1..60c8e6108c 100644 --- a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java @@ -1,3 +1,12 @@ +/* + * 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; diff --git a/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java b/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java index 76eb17b7cd..9ecaa9c4e8 100644 --- a/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java +++ b/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java @@ -1,3 +1,12 @@ +/* + * 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; From c4b5e17298b944e95958f6f2386f601a76066aec Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 3 Sep 2024 13:01:24 -0400 Subject: [PATCH 34/49] Add tests Signed-off-by: Craig Perkins --- .../identity/SecurityUserSubject.java | 1 - .../ContextProvidingPluginSubjectTests.java | 54 +++++++++++++++++++ .../identity/SecurityUserSubjectTests.java | 52 ++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java create mode 100644 src/test/java/org/opensearch/security/identity/SecurityUserSubjectTests.java diff --git a/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java b/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java index 9ecaa9c4e8..5fab960ffd 100644 --- a/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java +++ b/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java @@ -44,7 +44,6 @@ public Principal getPrincipal() { @Override public T runAs(Callable callable) throws Exception { try (ThreadContext.StoredContext ctx = threadPool.getThreadContext().stashContext()) { - System.out.println("Switching to user: " + user); threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); return callable.call(); } 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..562d97a8bd --- /dev/null +++ b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.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.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.user.PluginUser; +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 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 PluginUser pluginUser = new PluginUser(testPlugin.getClass().getCanonicalName()); + + ContextProvidingPluginSubject subject = new ContextProvidingPluginSubject(threadPool, Settings.EMPTY, testPlugin); + + 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)); + + SecurityUserSubjectTests.terminate(threadPool); + } +} diff --git a/src/test/java/org/opensearch/security/identity/SecurityUserSubjectTests.java b/src/test/java/org/opensearch/security/identity/SecurityUserSubjectTests.java new file mode 100644 index 0000000000..99ef34549e --- /dev/null +++ b/src/test/java/org/opensearch/security/identity/SecurityUserSubjectTests.java @@ -0,0 +1,52 @@ +/* + * 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.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 SecurityUserSubjectTests { + + 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"); + + SecurityUserSubject subject = new SecurityUserSubject(threadPool, user); + + 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); + } +} From 1d1bb571df835a22e7d85ee321eb181b4325ab3c Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 3 Sep 2024 13:29:04 -0400 Subject: [PATCH 35/49] Test PluginContextSwitcher Signed-off-by: Craig Perkins --- .../ContextProvidingPluginSubjectTests.java | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java index 562d97a8bd..b4a783e71a 100644 --- a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java +++ b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java @@ -51,4 +51,30 @@ public void testSecurityUserSubjectRunAs() throws Exception { SecurityUserSubjectTests.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 PluginUser pluginUser = new PluginUser(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)); + + SecurityUserSubjectTests.terminate(threadPool); + } } From 27be3c3bfe2a5027173047850a290aef36f48e1c Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 3 Sep 2024 13:32:16 -0400 Subject: [PATCH 36/49] Test getPrincipal Signed-off-by: Craig Perkins --- .../security/identity/ContextProvidingPluginSubjectTests.java | 2 ++ .../opensearch/security/identity/SecurityUserSubjectTests.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java index b4a783e71a..e73c8271f4 100644 --- a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java +++ b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java @@ -40,6 +40,8 @@ public void testSecurityUserSubjectRunAs() throws Exception { 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(() -> { diff --git a/src/test/java/org/opensearch/security/identity/SecurityUserSubjectTests.java b/src/test/java/org/opensearch/security/identity/SecurityUserSubjectTests.java index 99ef34549e..d6d9da3ba5 100644 --- a/src/test/java/org/opensearch/security/identity/SecurityUserSubjectTests.java +++ b/src/test/java/org/opensearch/security/identity/SecurityUserSubjectTests.java @@ -38,6 +38,8 @@ public void testSecurityUserSubjectRunAs() throws Exception { SecurityUserSubject subject = new SecurityUserSubject(threadPool, user); + assertThat(subject.getPrincipal().getName(), equalTo(user.getName())); + assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); subject.runAs(() -> { From 634cf7b7f03e893cbf0cd77c6680f1f9deaa0897 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 3 Sep 2024 14:11:54 -0400 Subject: [PATCH 37/49] Add test on createSecurityRole Signed-off-by: Craig Perkins --- .../security/privileges/PrivilegesEvaluator.java | 2 +- .../security/securityconf/ConfigModelV6.java | 9 ++++----- .../security/securityconf/ConfigModelV7.java | 9 ++++----- .../security/securityconf/SecurityRoles.java | 3 ++- .../ssl/transport/SecuritySSLRequestHandler.java | 2 +- .../SecurityRolesPermissionsV6Test.java | 14 ++++++++++++-- 6 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 903c5c2046..238284f747 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -199,7 +199,7 @@ public SecurityRoles getSecurityRoles(Set roles) { public SecurityRoles getSecurityRoleForPlugin(String pluginIdentifier) { SecurityRoles pluginRole = pluginRoles.get(pluginIdentifier); if (pluginRole == null) { - pluginRole = configModel.getSecurityRoles().createSecurityRole(pluginIdentifier, Set.of(BulkAction.NAME), Set.of(), Set.of()); + pluginRole = configModel.getSecurityRoles().createSecurityRole(pluginIdentifier, Set.of(BulkAction.NAME), Map.of()); pluginRoles.put(pluginIdentifier, pluginRole); } return pluginRole; diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java index dd296931c5..d859b75319 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java @@ -363,15 +363,14 @@ public SecurityRoles filter(Set keep) { public SecurityRoles createSecurityRole( String roleName, Set clusterPerms, - Set indexPatterns, - Set allowedActions + Map> indexPatternToAllowedActions ) { SecurityRole role = new SecurityRole(roleName); role.addClusterPerms(clusterPerms); - for (String ip : indexPatterns) { - IndexPattern idxPattern = new IndexPattern(ip); + for (Map.Entry> entry : indexPatternToAllowedActions.entrySet()) { + IndexPattern idxPattern = new IndexPattern(entry.getKey()); TypePerm perms = new TypePerm(""); - perms.addPerms(allowedActions); + perms.addPerms(entry.getValue()); idxPattern.addTypePerms(perms); } SecurityRoles roles = new SecurityRoles(1); diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index 263ff0b459..1ab0ba8edf 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java @@ -277,13 +277,12 @@ public SecurityRoles filter(Set keep) { public SecurityRoles createSecurityRole( String roleName, Set clusterPerms, - Set indexPatterns, - Set allowedActions + Map> indexPatternToAllowedActions ) { Set ipatterns = new HashSet<>(); - for (String ip : indexPatterns) { - IndexPattern idxPattern = new IndexPattern(ip); - idxPattern.addPerm(allowedActions); + for (Map.Entry> entry : indexPatternToAllowedActions.entrySet()) { + IndexPattern idxPattern = new IndexPattern(entry.getKey()); + idxPattern.addPerm(entry.getValue()); ipatterns.add(idxPattern); } SecurityRole role = new SecurityRole(roleName, ipatterns, WildcardMatcher.from(clusterPerms)); diff --git a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java index 8c03188c8d..7bf4ee81fd 100644 --- a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java +++ b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java @@ -27,6 +27,7 @@ package org.opensearch.security.securityconf; +import java.util.Map; import java.util.Set; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; @@ -97,7 +98,7 @@ Set getAllPermittedIndicesForDashboards( SecurityRoles filter(Set roles); - SecurityRoles createSecurityRole(String roleName, Set clusterPerms, Set indexPatterns, Set allowedActions); + SecurityRoles createSecurityRole(String roleName, Set clusterPerms, Map> indexPatternToAllowedActions); boolean isPermittedOnSystemIndex(String indexName); } diff --git a/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLRequestHandler.java b/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLRequestHandler.java index 758bd12132..7002171595 100644 --- a/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLRequestHandler.java +++ b/src/main/java/org/opensearch/security/ssl/transport/SecuritySSLRequestHandler.java @@ -47,7 +47,7 @@ public class SecuritySSLRequestHandler implements Tr private final String action; private final TransportRequestHandler actualHandler; - protected final ThreadPool threadPool; + private final ThreadPool threadPool; protected final Logger log = LogManager.getLogger(this.getClass()); private final PrincipalExtractor principalExtractor; private final SslExceptionHandler errorHandler; diff --git a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsV6Test.java b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsV6Test.java index 530db3211b..914f8750a0 100644 --- a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsV6Test.java +++ b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsV6Test.java @@ -15,6 +15,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeMap; import com.google.common.collect.ImmutableMap; @@ -41,6 +42,7 @@ import org.mockito.quality.Strictness; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -120,17 +122,25 @@ public void hasExplicitIndexPermission() { .filter(ImmutableSet.of("has_system_index_permission")); user.addSecurityRoles(List.of("has_system_index_permission")); - Assert.assertTrue( + assertTrue( "Should allow system index access with explicit only", securityRoleWithExplicitAccess.hasExplicitIndexPermission(resolved, user, new String[] {}, resolver, cs) ); } + @Test + public void testCreateSecurityRole() { + SecurityRoles securityRoles = configModel.getSecurityRoles() + .createSecurityRole("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")); + } + @Test public void isPermittedOnSystemIndex() { final SecurityRoles securityRoleWithExplicitAccess = configModel.getSecurityRoles() .filter(ImmutableSet.of("has_system_index_permission")); - Assert.assertTrue(securityRoleWithExplicitAccess.isPermittedOnSystemIndex(TEST_INDEX)); + assertTrue(securityRoleWithExplicitAccess.isPermittedOnSystemIndex(TEST_INDEX)); final SecurityRoles securityRoleWithStarAccess = configModel.getSecurityRoles() .filter(ImmutableSet.of("all_access_without_system_index_permission")); From 1af4b65f1514022ccac1caf816432f45167e331f Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 3 Sep 2024 16:13:35 -0400 Subject: [PATCH 38/49] Increase test coverage Signed-off-by: Craig Perkins --- .../ContextProvidingPluginSubjectTests.java | 21 +++++++++++++++++++ .../SecurityRolesPermissionsTest.java | 10 +++++++++ 2 files changed, 31 insertions(+) diff --git a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java index e73c8271f4..b9ba435d0a 100644 --- a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java +++ b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java @@ -79,4 +79,25 @@ public void testPluginContextSwitcherRunAs() throws Exception { SecurityUserSubjectTests.terminate(threadPool); } + + @Test + public void testPluginContextSwitcherUninitializedRunAs() throws Exception { + final ThreadPool threadPool = new TestThreadPool(getClass().getName()); + + final PluginContextSwitcher contextSwitcher = new PluginContextSwitcher(); + + threadPool.getThreadContext().putHeader("foo", "bar"); + + // should be no noop since contextSwitch is uninitialized + Object returnVal = contextSwitcher.runAs(() -> { + assertThat(threadPool.getThreadContext().getHeader("foo"), equalTo("bar")); + return null; + }); + + assertThat(threadPool.getThreadContext().getHeader("foo"), equalTo("bar")); + + assertNull(returnVal); + + SecurityUserSubjectTests.terminate(threadPool); + } } diff --git a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java index f469d1989c..e9c10da015 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; @@ -55,6 +56,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 { @@ -217,6 +219,14 @@ public void hasExplicitClusterPermissionPermissionForRestAdmin() { assertHasNoPermissionsForRestApiAdminOnePermissionRole(Endpoint.CONFIG, securityConfigAllowRole); } + @Test + public void testCreateSecurityRole() { + SecurityRoles securityRoles = configModel.getSecurityRoles() + .createSecurityRole("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() From 64fb16123d2a7bec55558188296f2c786fe8c3a8 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 3 Sep 2024 16:40:03 -0400 Subject: [PATCH 39/49] Allow import for AcknowledgedResponse Signed-off-by: Craig Perkins --- .../security/plugin/IndexDocumentIntoSystemIndexResponse.java | 2 ++ .../opensearch/security/plugin/RunClusterHealthResponse.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java index cc72c64887..9779cca252 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/IndexDocumentIntoSystemIndexResponse.java @@ -10,6 +10,7 @@ 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; @@ -18,6 +19,7 @@ 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 { diff --git a/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthResponse.java b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthResponse.java index e171d9213a..7a855744dc 100644 --- a/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthResponse.java +++ b/src/integrationTest/java/org/opensearch/security/plugin/RunClusterHealthResponse.java @@ -10,6 +10,7 @@ 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; @@ -17,6 +18,7 @@ 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 { From dc8868e8a75ba554c25580d6fdddc9dd56d5bd09 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 6 Sep 2024 13:56:49 -0400 Subject: [PATCH 40/49] Address code review comments Signed-off-by: Craig Perkins --- .../security/OpenSearchSecurityPlugin.java | 4 ++-- .../security/auth/BackendRegistry.java | 6 +++--- .../identity/PluginContextSwitcher.java | 5 ++--- .../privileges/SnapshotRestoreEvaluator.java | 2 +- .../privileges/SystemIndexAccessEvaluator.java | 2 +- .../security/support/ConfigConstants.java | 2 +- .../security/transport/SecurityInterceptor.java | 4 ++-- .../transport/SecurityRequestHandler.java | 6 ------ .../ContextProvidingPluginSubjectTests.java | 17 ++--------------- 9 files changed, 14 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 9fde77ff87..2009efd23d 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -220,7 +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_SUBJECT; +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; @@ -2106,7 +2106,7 @@ private static String handleKeyword(final String field) { @Override public Subject getCurrentSubject() { - return (Subject) threadPool.getThreadContext().getPersistent(OPENDISTRO_SECURITY_SUBJECT); + return (Subject) threadPool.getThreadContext().getPersistent(OPENDISTRO_SECURITY_AUTHENTICATED_USER); } @Override diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index dce8087ea3..229dfdf32d 100644 --- a/src/main/java/org/opensearch/security/auth/BackendRegistry.java +++ b/src/main/java/org/opensearch/security/auth/BackendRegistry.java @@ -226,7 +226,7 @@ public boolean authenticate(final SecurityRequestChannel request) { // PKI authenticated REST call User superuser = new User(sslPrincipal); UserSubject subject = new SecurityUserSubject(threadPool, superuser); - threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_SUBJECT, subject); + threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, subject); threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, superuser); auditLog.logSucceededLogin(sslPrincipal, true, null, request); return true; @@ -391,7 +391,7 @@ public boolean authenticate(final SecurityRequestChannel request) { threadPool.getThreadContext() .putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, impersonatedUser == null ? authenticatedUser : impersonatedUser); UserSubject subject = new SecurityUserSubject(threadPool, impersonatedUser == null ? authenticatedUser : impersonatedUser); - threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_SUBJECT, subject); + threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, subject); auditLog.logSucceededLogin( (impersonatedUser == null ? authenticatedUser : impersonatedUser).getName(), false, @@ -427,7 +427,7 @@ public boolean authenticate(final SecurityRequestChannel request) { UserSubject subject = new SecurityUserSubject(threadPool, anonymousUser); threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, anonymousUser); - threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_SUBJECT, subject); + 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/identity/PluginContextSwitcher.java b/src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java index 52809d4fab..a16671fda4 100644 --- a/src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java +++ b/src/main/java/org/opensearch/security/identity/PluginContextSwitcher.java @@ -10,6 +10,7 @@ */ package org.opensearch.security.identity; +import java.util.Objects; import java.util.concurrent.Callable; import org.opensearch.identity.PluginSubject; @@ -24,9 +25,7 @@ public void initialize(PluginSubject pluginSubject) { } public T runAs(Callable callable) { - if (pluginSubject == null) { - return null; - } + Objects.requireNonNull(pluginSubject); try { return pluginSubject.runAs(callable); } catch (Exception e) { diff --git a/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java b/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java index b206442585..8a11e433e8 100644 --- a/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SnapshotRestoreEvaluator.java @@ -109,7 +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.missingPrivileges.add(action); + 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 89adab64e8..4210dbb931 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -288,7 +288,7 @@ private void evaluateSystemIndicesAccess( presponse.markComplete(); } else { if (log.isInfoEnabled()) { - log.info( + log.warn( "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, diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index ab99275897..91000e44f2 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -113,7 +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_SUBJECT = OPENDISTRO_SECURITY_CONFIG_PREFIX + "subject"; + 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 b73118fc91..e88b91487f 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java +++ b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java @@ -131,8 +131,8 @@ public SecurityRequestHandler getHandler(String private User determineUser(Connection connection) { User user0 = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - // pluginUser did not exist prior to 2.17.0 - if (user0 != null && user0.isPluginUser() && connection.getVersion().before(Version.V_2_17_0)) { + // 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; diff --git a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java index 8cff86a2dc..d2dbe50ab7 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java +++ b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java @@ -182,12 +182,6 @@ protected void messageReceivedDecorate( } else { User deserializedUser = (User) Base64Helper.deserializeObject(userHeader, useJDKSerialization); getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, deserializedUser); - // if (!deserializedUser.isPluginUser()) { - // getThreadContext().putPersistent( - // ConfigConstants.OPENDISTRO_SECURITY_SUBJECT, - // new SecurityUserSubject(threadPool, deserializedUser) - // ); - // } } String originalRemoteAddress = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER); diff --git a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java index b9ba435d0a..61be66ac11 100644 --- a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java +++ b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java @@ -24,6 +24,7 @@ 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 { @@ -82,22 +83,8 @@ public void testPluginContextSwitcherRunAs() throws Exception { @Test public void testPluginContextSwitcherUninitializedRunAs() throws Exception { - final ThreadPool threadPool = new TestThreadPool(getClass().getName()); - final PluginContextSwitcher contextSwitcher = new PluginContextSwitcher(); - threadPool.getThreadContext().putHeader("foo", "bar"); - - // should be no noop since contextSwitch is uninitialized - Object returnVal = contextSwitcher.runAs(() -> { - assertThat(threadPool.getThreadContext().getHeader("foo"), equalTo("bar")); - return null; - }); - - assertThat(threadPool.getThreadContext().getHeader("foo"), equalTo("bar")); - - assertNull(returnVal); - - SecurityUserSubjectTests.terminate(threadPool); + assertThrows(NullPointerException.class, () -> contextSwitcher.runAs(() -> null)); } } From 565fa2056bda13f339e73e054c39f2d55e000680 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 6 Sep 2024 14:12:40 -0400 Subject: [PATCH 41/49] Address code review feedback Signed-off-by: Craig Perkins --- .../opensearch/security/SystemIndexTests.java | 14 +++--- .../test/framework/cluster/LocalCluster.java | 14 ++---- .../SystemIndexAccessEvaluator.java | 49 ++++++++++--------- 3 files changed, 36 insertions(+), 41 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 64266b22d9..75be734f2f 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -35,7 +35,6 @@ 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.security.support.ConfigConstants.SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY; import static org.opensearch.test.framework.TestSecurityConfig.Role.ALL_ACCESS; import static org.opensearch.test.framework.TestSecurityConfig.User.USER_ADMIN; @@ -50,13 +49,11 @@ public class SystemIndexTests { .anonymousAuth(false) .authc(AUTHC_DOMAIN) .users(USER_ADMIN) - .plugin(List.of(SystemIndexPlugin1.class, SystemIndexPlugin2.class)) + .plugin(SystemIndexPlugin1.class, SystemIndexPlugin2.class) .nodeSettings( Map.of( FeatureFlags.IDENTITY, true, - SECURITY_SYSTEM_INDICES_PERMISSIONS_ENABLED_KEY, - true, SECURITY_RESTAPI_ROLES_ENABLED, List.of("user_" + USER_ADMIN.getName() + "__" + ALL_ACCESS.getName()), SECURITY_SYSTEM_INDICES_ENABLED_KEY, @@ -88,10 +85,15 @@ public void adminShouldNotBeAbleToDeleteSecurityIndex() { assertThat(response2.getStatusCode(), equalTo(RestStatus.OK.getStatus())); - // regular use cannot create system index when system index protection is enforced + // regular user can create system index HttpResponse response3 = client.put(".system-index1"); - assertThat(response3.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); + assertThat(response3.getStatusCode(), equalTo(RestStatus.OK.getStatus())); + + // regular user cannot delete system index + HttpResponse response4 = client.delete(".system-index1"); + + assertThat(response4.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); } } 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 9d0b084655..d3998e4793 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java +++ b/src/integrationTest/java/org/opensearch/test/framework/cluster/LocalCluster.java @@ -378,20 +378,12 @@ public Builder nodeSettings(Map settings) { return this; } - /** - * Adds additional plugin to the cluster - */ - public Builder plugin(Class plugin) { - this.plugins.add(plugin); - - return this; - } - /** * Adds additional plugins to the cluster */ - public Builder plugin(List> plugins) { - this.plugins.addAll(plugins); + @SafeVarargs + public final Builder plugin(Class... plugins) { + this.plugins.addAll(List.of(plugins)); return this; } diff --git a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java index 4210dbb931..6b58f60d56 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -277,30 +277,6 @@ private void evaluateSystemIndicesAccess( return; } boolean containsProtectedIndex = requestContainsAnyProtectedSystemIndices(requestedResolved); - if (user.isPluginUser()) { - Set matchingSystemIndices = SystemIndexRegistry.matchesPluginSystemIndexPattern( - user.getName(), - 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.warn( - "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 (containsProtectedIndex) { auditLog.logSecurityIndexAttempt(request, action, task); @@ -340,6 +316,31 @@ private void evaluateSystemIndicesAccess( } } + if (user.isPluginUser()) { + Set matchingSystemIndices = SystemIndexRegistry.matchesPluginSystemIndexPattern( + user.getName(), + 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) { From e5855ab4bb31ea86b74bd35ab27d03309a6751d4 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 20 Sep 2024 15:16:45 -0400 Subject: [PATCH 42/49] Change calls to isPluginUser and create InMemorySecurityRoles Signed-off-by: Craig Perkins --- .../privileges/PrivilegesEvaluator.java | 12 ++++++--- .../SystemIndexAccessEvaluator.java | 3 ++- .../security/securityconf/ConfigModelV6.java | 19 -------------- .../security/securityconf/ConfigModelV7.java | 24 +++-------------- .../securityconf/InMemorySecurityRoles.java | 20 ++++++++++++++ .../securityconf/InMemorySecurityRolesV7.java | 26 +++++++++++++++++++ .../security/securityconf/SecurityRoles.java | 3 --- .../transport/SecurityInterceptor.java | 3 ++- .../org/opensearch/security/user/User.java | 7 ----- .../SecurityRolesPermissionsTest.java | 4 +-- .../SecurityRolesPermissionsV6Test.java | 9 ------- 11 files changed, 63 insertions(+), 67 deletions(-) create mode 100644 src/main/java/org/opensearch/security/securityconf/InMemorySecurityRoles.java create mode 100644 src/main/java/org/opensearch/security/securityconf/InMemorySecurityRolesV7.java diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 238284f747..c984360cd6 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -90,10 +90,13 @@ 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; import org.opensearch.security.support.WildcardMatcher; +import org.opensearch.security.user.PluginUser; import org.opensearch.security.user.User; import org.opensearch.tasks.Task; import org.opensearch.threadpool.ThreadPool; @@ -143,7 +146,7 @@ public class PrivilegesEvaluator { private final PitPrivilegesEvaluator pitPrivilegesEvaluator; private DynamicConfigModel dcm; private final NamedXContentRegistry namedXContentRegistry; - private final Map pluginRoles; + private final Map pluginRoles; public PrivilegesEvaluator( final ClusterService clusterService, @@ -197,9 +200,10 @@ public SecurityRoles getSecurityRoles(Set roles) { } public SecurityRoles getSecurityRoleForPlugin(String pluginIdentifier) { - SecurityRoles pluginRole = pluginRoles.get(pluginIdentifier); + InMemorySecurityRoles pluginRole = pluginRoles.get(pluginIdentifier); if (pluginRole == null) { - pluginRole = configModel.getSecurityRoles().createSecurityRole(pluginIdentifier, Set.of(BulkAction.NAME), Map.of()); + pluginRole = new InMemorySecurityRolesV7(1); + pluginRole.addSecurityRole(pluginIdentifier, Set.of(BulkAction.NAME), Map.of()); pluginRoles.put(pluginIdentifier, pluginRole); } return pluginRole; @@ -292,7 +296,7 @@ public PrivilegesEvaluatorResponse evaluate(PrivilegesEvaluationContext context) } presponse.resolvedSecurityRoles.addAll(mappedRoles); final SecurityRoles securityRoles; - if (user.isPluginUser()) { + if (user instanceof PluginUser) { securityRoles = getSecurityRoleForPlugin(user.getName()); } else { securityRoles = getSecurityRoles(mappedRoles); diff --git a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java index 6b58f60d56..6e1168b214 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -48,6 +48,7 @@ import org.opensearch.security.securityconf.SecurityRoles; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.WildcardMatcher; +import org.opensearch.security.user.PluginUser; import org.opensearch.security.user.User; import org.opensearch.tasks.Task; @@ -316,7 +317,7 @@ private void evaluateSystemIndicesAccess( } } - if (user.isPluginUser()) { + if (user instanceof PluginUser) { Set matchingSystemIndices = SystemIndexRegistry.matchesPluginSystemIndexPattern( user.getName(), requestedResolved.getAllIndices() diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java index d859b75319..e35fb40a24 100644 --- a/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java +++ b/src/main/java/org/opensearch/security/securityconf/ConfigModelV6.java @@ -359,25 +359,6 @@ public SecurityRoles filter(Set keep) { return retVal; } - @Override - public SecurityRoles createSecurityRole( - String roleName, - Set clusterPerms, - Map> indexPatternToAllowedActions - ) { - SecurityRole role = new SecurityRole(roleName); - role.addClusterPerms(clusterPerms); - for (Map.Entry> entry : indexPatternToAllowedActions.entrySet()) { - IndexPattern idxPattern = new IndexPattern(entry.getKey()); - TypePerm perms = new TypePerm(""); - perms.addPerms(entry.getValue()); - idxPattern.addTypePerms(perms); - } - SecurityRoles roles = new SecurityRoles(1); - roles.addSecurityRole(role); - return roles; - } - @Override public EvaluatedDlsFlsConfig getDlsFls( User user, diff --git a/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java b/src/main/java/org/opensearch/security/securityconf/ConfigModelV7.java index 1ab0ba8edf..e41bbf9400 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); } @@ -273,24 +273,6 @@ public SecurityRoles filter(Set keep) { return retVal; } - @Override - public SecurityRoles createSecurityRole( - String roleName, - Set clusterPerms, - Map> indexPatternToAllowedActions - ) { - Set ipatterns = new HashSet<>(); - for (Map.Entry> entry : indexPatternToAllowedActions.entrySet()) { - IndexPattern idxPattern = new IndexPattern(entry.getKey()); - idxPattern.addPerm(entry.getValue()); - ipatterns.add(idxPattern); - } - SecurityRole role = new SecurityRole(roleName, ipatterns, WildcardMatcher.from(clusterPerms)); - SecurityRoles roles = new SecurityRoles(1); - roles.addSecurityRole(role); - return roles; - } - @Override public EvaluatedDlsFlsConfig getDlsFls( User user, @@ -554,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..9c160a3bd7 --- /dev/null +++ b/src/main/java/org/opensearch/security/securityconf/InMemorySecurityRolesV7.java @@ -0,0 +1,26 @@ +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/securityconf/SecurityRoles.java b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java index 7bf4ee81fd..fb25e1a21f 100644 --- a/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java +++ b/src/main/java/org/opensearch/security/securityconf/SecurityRoles.java @@ -27,7 +27,6 @@ package org.opensearch.security.securityconf; -import java.util.Map; import java.util.Set; import org.opensearch.cluster.metadata.IndexNameExpressionResolver; @@ -98,7 +97,5 @@ Set getAllPermittedIndicesForDashboards( SecurityRoles filter(Set roles); - SecurityRoles createSecurityRole(String roleName, Set clusterPerms, Map> indexPatternToAllowedActions); - boolean isPermittedOnSystemIndex(String indexName); } diff --git a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java index 099e025c31..f32908a0a7 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java +++ b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java @@ -64,6 +64,7 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.HeaderHelper; import org.opensearch.security.support.SerializationFormat; +import org.opensearch.security.user.PluginUser; import org.opensearch.security.user.User; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.Transport.Connection; @@ -132,7 +133,7 @@ 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)) { + if (user0 != null && user0 instanceof PluginUser && connection.getVersion().before(Version.V_2_18_0)) { user0 = null; } return user0; diff --git a/src/main/java/org/opensearch/security/user/User.java b/src/main/java/org/opensearch/security/user/User.java index 67e557bf77..ed48ab7356 100644 --- a/src/main/java/org/opensearch/security/user/User.java +++ b/src/main/java/org/opensearch/security/user/User.java @@ -296,11 +296,4 @@ public boolean isServiceAccount() { Map userAttributesMap = this.getCustomAttributesMap(); return userAttributesMap != null && "true".equals(userAttributesMap.get("attr.internal.service")); } - - /** - * @return true if this instance is of the type PluginUser - */ - public boolean isPluginUser() { - return this instanceof PluginUser; - } } diff --git a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java index e9c10da015..b783cfb40d 100644 --- a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java +++ b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsTest.java @@ -221,8 +221,8 @@ public void hasExplicitClusterPermissionPermissionForRestAdmin() { @Test public void testCreateSecurityRole() { - SecurityRoles securityRoles = configModel.getSecurityRoles() - .createSecurityRole("testRole", Set.of("cluster:monitor/health"), Map.of("*", Set.of("indices:data/read/search"))); + 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")); } diff --git a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsV6Test.java b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsV6Test.java index 914f8750a0..281704a4ed 100644 --- a/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsV6Test.java +++ b/src/test/java/org/opensearch/security/securityconf/SecurityRolesPermissionsV6Test.java @@ -15,7 +15,6 @@ import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.TreeMap; import com.google.common.collect.ImmutableMap; @@ -128,14 +127,6 @@ public void hasExplicitIndexPermission() { ); } - @Test - public void testCreateSecurityRole() { - SecurityRoles securityRoles = configModel.getSecurityRoles() - .createSecurityRole("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")); - } - @Test public void isPermittedOnSystemIndex() { final SecurityRoles securityRoleWithExplicitAccess = configModel.getSecurityRoles() From b8c333c5a0fd3c11ca38ce7761fad2e36629a621 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Fri, 20 Sep 2024 16:52:04 -0400 Subject: [PATCH 43/49] Add missing license header Signed-off-by: Craig Perkins --- .../securityconf/InMemorySecurityRolesV7.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/main/java/org/opensearch/security/securityconf/InMemorySecurityRolesV7.java b/src/main/java/org/opensearch/security/securityconf/InMemorySecurityRolesV7.java index 9c160a3bd7..da5a07298c 100644 --- a/src/main/java/org/opensearch/security/securityconf/InMemorySecurityRolesV7.java +++ b/src/main/java/org/opensearch/security/securityconf/InMemorySecurityRolesV7.java @@ -1,3 +1,14 @@ +/* + * 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; From 9b0c3df1a9b2549aba309b11fe7f445d9ca764ed Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Mon, 23 Sep 2024 14:12:08 -0400 Subject: [PATCH 44/49] Use plugin: prefix Signed-off-by: Craig Perkins --- .../opensearch/security/SystemIndexTests.java | 8 +++-- .../ContextProvidingPluginSubject.java | 8 +++-- .../privileges/PrivilegesEvaluator.java | 3 +- .../SystemIndexAccessEvaluator.java | 5 ++- .../support/SafeSerializationUtils.java | 2 -- .../transport/SecurityInterceptor.java | 3 +- .../opensearch/security/user/PluginUser.java | 34 ------------------- .../org/opensearch/security/user/User.java | 9 +++++ 8 files changed, 23 insertions(+), 49 deletions(-) delete mode 100644 src/main/java/org/opensearch/security/user/PluginUser.java diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 75be734f2f..97254a8406 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -115,7 +115,9 @@ public void testPluginShouldNotBeAbleToIndexDocumentIntoSystemIndexRegisteredByO assertThat(response.getStatusCode(), equalTo(RestStatus.FORBIDDEN.getStatus())); assertThat( response.getBody(), - containsString("no permissions for [indices:admin/create] and User [name=org.opensearch.security.plugin.SystemIndexPlugin1") + containsString( + "no permissions for [indices:admin/create] and User [name=plugin:org.opensearch.security.plugin.SystemIndexPlugin1" + ) ); } } @@ -139,7 +141,7 @@ public void testPluginShouldNotBeAbleToRunClusterActions() { assertThat( response.getBody(), containsString( - "no permissions for [cluster:monitor/health] and User [name=org.opensearch.security.plugin.SystemIndexPlugin1" + "no permissions for [cluster:monitor/health] and User [name=plugin:org.opensearch.security.plugin.SystemIndexPlugin1" ) ); } @@ -184,7 +186,7 @@ public void testPluginShouldNotBeAbleToBulkIndexDocumentIntoMixOfSystemIndexWher assertThat( response.getBody(), containsString( - "no permissions for [indices:data/write/bulk[s]] and User [name=org.opensearch.security.plugin.SystemIndexPlugin1" + "no permissions for [indices:data/write/bulk[s]] and User [name=plugin:org.opensearch.security.plugin.SystemIndexPlugin1" ) ); } diff --git a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java index 60c8e6108c..7a096abf08 100644 --- a/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java +++ b/src/main/java/org/opensearch/security/identity/ContextProvidingPluginSubject.java @@ -18,19 +18,21 @@ import org.opensearch.identity.PluginSubject; import org.opensearch.plugins.Plugin; import org.opensearch.security.support.ConfigConstants; -import org.opensearch.security.user.PluginUser; +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 PluginUser pluginUser; + private final User pluginUser; public ContextProvidingPluginSubject(ThreadPool threadPool, Settings settings, Plugin plugin) { super(); this.threadPool = threadPool; this.pluginPrincipal = new NamedPrincipal(plugin.getClass().getCanonicalName()); - this.pluginUser = new PluginUser(pluginPrincipal.getName()); + // 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 diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index c984360cd6..195f819f7b 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -96,7 +96,6 @@ import org.opensearch.security.securityconf.impl.DashboardSignInOption; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.WildcardMatcher; -import org.opensearch.security.user.PluginUser; import org.opensearch.security.user.User; import org.opensearch.tasks.Task; import org.opensearch.threadpool.ThreadPool; @@ -296,7 +295,7 @@ public PrivilegesEvaluatorResponse evaluate(PrivilegesEvaluationContext context) } presponse.resolvedSecurityRoles.addAll(mappedRoles); final SecurityRoles securityRoles; - if (user instanceof PluginUser) { + if (user.isPluginUser()) { securityRoles = getSecurityRoleForPlugin(user.getName()); } else { securityRoles = getSecurityRoles(mappedRoles); diff --git a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java index 6e1168b214..00a2791ba3 100644 --- a/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/SystemIndexAccessEvaluator.java @@ -48,7 +48,6 @@ import org.opensearch.security.securityconf.SecurityRoles; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.WildcardMatcher; -import org.opensearch.security.user.PluginUser; import org.opensearch.security.user.User; import org.opensearch.tasks.Task; @@ -317,9 +316,9 @@ private void evaluateSystemIndicesAccess( } } - if (user instanceof PluginUser) { + if (user.isPluginUser()) { Set matchingSystemIndices = SystemIndexRegistry.matchesPluginSystemIndexPattern( - user.getName(), + user.getName().replace("plugin:", ""), requestedResolved.getAllIndices() ); if (requestedResolved.getAllIndices().equals(matchingSystemIndices)) { diff --git a/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java b/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java index 1d2289f0ae..b58e4afd35 100644 --- a/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java +++ b/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java @@ -26,7 +26,6 @@ import com.google.common.collect.ImmutableSet; import org.opensearch.security.auth.UserInjector; -import org.opensearch.security.user.PluginUser; import org.opensearch.security.user.User; import com.amazon.dlic.auth.ldap.LdapUser; @@ -49,7 +48,6 @@ public final class SafeSerializationUtils { InetSocketAddress.class, Pattern.class, User.class, - PluginUser.class, UserInjector.InjectedUser.class, SourceFieldsContext.class, LdapUser.class, diff --git a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java index f32908a0a7..099e025c31 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java +++ b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java @@ -64,7 +64,6 @@ import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.HeaderHelper; import org.opensearch.security.support.SerializationFormat; -import org.opensearch.security.user.PluginUser; import org.opensearch.security.user.User; import org.opensearch.threadpool.ThreadPool; import org.opensearch.transport.Transport.Connection; @@ -133,7 +132,7 @@ 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 instanceof PluginUser && connection.getVersion().before(Version.V_2_18_0)) { + if (user0 != null && user0.isPluginUser() && connection.getVersion().before(Version.V_2_18_0)) { user0 = null; } return user0; diff --git a/src/main/java/org/opensearch/security/user/PluginUser.java b/src/main/java/org/opensearch/security/user/PluginUser.java deleted file mode 100644 index 66ed1b0149..0000000000 --- a/src/main/java/org/opensearch/security/user/PluginUser.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.user; - -import java.io.IOException; - -import org.opensearch.core.common.io.stream.StreamInput; - -public class PluginUser extends User { - private static final long serialVersionUID = -4083322940729403322L; - - public PluginUser(StreamInput in) throws IOException { - super(in); - } - - /** - * Create a new plugin user without roles and attributes - * - * @param name The username (must not be null or empty) - * @throws IllegalArgumentException if name is null or empty - */ - public PluginUser(final String name) { - super(name, null, null); - } -} diff --git a/src/main/java/org/opensearch/security/user/User.java b/src/main/java/org/opensearch/security/user/User.java index ed48ab7356..190729623d 100644 --- a/src/main/java/org/opensearch/security/user/User.java +++ b/src/main/java/org/opensearch/security/user/User.java @@ -296,4 +296,13 @@ 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:"); + } } From abea484a218a64c05fb713ab0a02ad444d89fea1 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Mon, 23 Sep 2024 14:26:38 -0400 Subject: [PATCH 45/49] Remove PluginUser Signed-off-by: Craig Perkins --- .../identity/ContextProvidingPluginSubjectTests.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java index 61be66ac11..533e2eff83 100644 --- a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java +++ b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java @@ -16,7 +16,7 @@ import org.opensearch.common.settings.Settings; import org.opensearch.plugins.IdentityAwarePlugin; import org.opensearch.plugins.Plugin; -import org.opensearch.security.user.PluginUser; +import org.opensearch.security.user.User; import org.opensearch.threadpool.TestThreadPool; import org.opensearch.threadpool.ThreadPool; @@ -37,7 +37,7 @@ public void testSecurityUserSubjectRunAs() throws Exception { final Plugin testPlugin = new TestIdentityAwarePlugin(); - final PluginUser pluginUser = new PluginUser(testPlugin.getClass().getCanonicalName()); + final User pluginUser = new User(testPlugin.getClass().getCanonicalName()); ContextProvidingPluginSubject subject = new ContextProvidingPluginSubject(threadPool, Settings.EMPTY, testPlugin); @@ -63,7 +63,7 @@ public void testPluginContextSwitcherRunAs() throws Exception { final PluginContextSwitcher contextSwitcher = new PluginContextSwitcher(); - final PluginUser pluginUser = new PluginUser(testPlugin.getClass().getCanonicalName()); + final User pluginUser = new User(testPlugin.getClass().getCanonicalName()); ContextProvidingPluginSubject subject = new ContextProvidingPluginSubject(threadPool, Settings.EMPTY, testPlugin); From ed13ecbfc09159f8b1e53273d6b65f2732da8c5b Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 1 Oct 2024 16:52:58 -0400 Subject: [PATCH 46/49] Remove Identity FeatureFlag Signed-off-by: Craig Perkins --- .../java/org/opensearch/security/SystemIndexTests.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java index 97254a8406..c2a5a9f89a 100644 --- a/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java +++ b/src/integrationTest/java/org/opensearch/security/SystemIndexTests.java @@ -18,7 +18,6 @@ import org.junit.Test; import org.junit.runner.RunWith; -import org.opensearch.common.util.FeatureFlags; import org.opensearch.core.rest.RestStatus; import org.opensearch.security.plugin.SystemIndexPlugin1; import org.opensearch.security.plugin.SystemIndexPlugin2; @@ -52,8 +51,6 @@ public class SystemIndexTests { .plugin(SystemIndexPlugin1.class, SystemIndexPlugin2.class) .nodeSettings( Map.of( - FeatureFlags.IDENTITY, - true, SECURITY_RESTAPI_ROLES_ENABLED, List.of("user_" + USER_ADMIN.getName() + "__" + ALL_ACCESS.getName()), SECURITY_SYSTEM_INDICES_ENABLED_KEY, From 68ff2705e075229b10a92fa02b5227633efa3513 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Tue, 1 Oct 2024 18:59:03 -0400 Subject: [PATCH 47/49] Fix tests Signed-off-by: Craig Perkins --- .../security/identity/ContextProvidingPluginSubjectTests.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java index 533e2eff83..0b98913ab8 100644 --- a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java +++ b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java @@ -37,7 +37,7 @@ public void testSecurityUserSubjectRunAs() throws Exception { final Plugin testPlugin = new TestIdentityAwarePlugin(); - final User pluginUser = new User(testPlugin.getClass().getCanonicalName()); + final User pluginUser = new User("plugin:" + testPlugin.getClass().getCanonicalName()); ContextProvidingPluginSubject subject = new ContextProvidingPluginSubject(threadPool, Settings.EMPTY, testPlugin); @@ -63,7 +63,7 @@ public void testPluginContextSwitcherRunAs() throws Exception { final PluginContextSwitcher contextSwitcher = new PluginContextSwitcher(); - final User pluginUser = new User(testPlugin.getClass().getCanonicalName()); + final User pluginUser = new User("plugin:" + testPlugin.getClass().getCanonicalName()); ContextProvidingPluginSubject subject = new ContextProvidingPluginSubject(threadPool, Settings.EMPTY, testPlugin); From 0216cc051dd2b106dfd0d178959cc3caa160c7a2 Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Mon, 14 Oct 2024 15:34:57 -0400 Subject: [PATCH 48/49] Address code review feedback Signed-off-by: Craig Perkins --- .../java/org/opensearch/security/auth/BackendRegistry.java | 7 +++---- .../SecurityUserSubject.java => auth/SecurityUser.java} | 6 +++--- .../SecurityUserTests.java} | 6 +++--- .../identity/ContextProvidingPluginSubjectTests.java | 5 +++-- 4 files changed, 12 insertions(+), 12 deletions(-) rename src/main/java/org/opensearch/security/{identity/SecurityUserSubject.java => auth/SecurityUser.java} (89%) rename src/test/java/org/opensearch/security/{identity/SecurityUserSubjectTests.java => auth/SecurityUserTests.java} (90%) diff --git a/src/main/java/org/opensearch/security/auth/BackendRegistry.java b/src/main/java/org/opensearch/security/auth/BackendRegistry.java index fb8eae59fe..5be7e9622a 100644 --- a/src/main/java/org/opensearch/security/auth/BackendRegistry.java +++ b/src/main/java/org/opensearch/security/auth/BackendRegistry.java @@ -64,7 +64,6 @@ import org.opensearch.security.filter.SecurityRequestChannel; import org.opensearch.security.filter.SecurityResponse; import org.opensearch.security.http.XFFResolver; -import org.opensearch.security.identity.SecurityUserSubject; import org.opensearch.security.securityconf.DynamicConfigModel; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.WildcardMatcher; @@ -226,7 +225,7 @@ public boolean authenticate(final SecurityRequestChannel request) { if (adminDns.isAdminDN(sslPrincipal)) { // PKI authenticated REST call User superuser = new User(sslPrincipal); - UserSubject subject = new SecurityUserSubject(threadPool, superuser); + 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); @@ -394,7 +393,7 @@ 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 SecurityUserSubject(threadPool, 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(), @@ -428,7 +427,7 @@ 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 SecurityUserSubject(threadPool, anonymousUser); + UserSubject subject = new SecurityUser(threadPool, anonymousUser); threadPool.getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, anonymousUser); threadPool.getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, subject); diff --git a/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java b/src/main/java/org/opensearch/security/auth/SecurityUser.java similarity index 89% rename from src/main/java/org/opensearch/security/identity/SecurityUserSubject.java rename to src/main/java/org/opensearch/security/auth/SecurityUser.java index 5fab960ffd..8ce4bfb3a1 100644 --- a/src/main/java/org/opensearch/security/identity/SecurityUserSubject.java +++ b/src/main/java/org/opensearch/security/auth/SecurityUser.java @@ -7,7 +7,7 @@ * compatible open source license. * */ -package org.opensearch.security.identity; +package org.opensearch.security.auth; import java.security.Principal; import java.util.concurrent.Callable; @@ -20,12 +20,12 @@ import org.opensearch.security.user.User; import org.opensearch.threadpool.ThreadPool; -public class SecurityUserSubject implements UserSubject { +public class SecurityUser implements UserSubject { private final NamedPrincipal userPrincipal; private final ThreadPool threadPool; private final User user; - public SecurityUserSubject(ThreadPool threadPool, User user) { + SecurityUser(ThreadPool threadPool, User user) { this.threadPool = threadPool; this.user = user; this.userPrincipal = new NamedPrincipal(user.getName()); diff --git a/src/test/java/org/opensearch/security/identity/SecurityUserSubjectTests.java b/src/test/java/org/opensearch/security/auth/SecurityUserTests.java similarity index 90% rename from src/test/java/org/opensearch/security/identity/SecurityUserSubjectTests.java rename to src/test/java/org/opensearch/security/auth/SecurityUserTests.java index d6d9da3ba5..4ae15ef303 100644 --- a/src/test/java/org/opensearch/security/identity/SecurityUserSubjectTests.java +++ b/src/test/java/org/opensearch/security/auth/SecurityUserTests.java @@ -9,7 +9,7 @@ * GitHub history for details. */ -package org.opensearch.security.identity; +package org.opensearch.security.auth; import java.util.concurrent.TimeUnit; @@ -24,7 +24,7 @@ import static org.opensearch.security.support.ConfigConstants.OPENDISTRO_SECURITY_USER; import static org.junit.Assert.assertNull; -public class SecurityUserSubjectTests { +public class SecurityUserTests { public static boolean terminate(ThreadPool threadPool) { return ThreadPool.terminate(threadPool, 10, TimeUnit.SECONDS); @@ -36,7 +36,7 @@ public void testSecurityUserSubjectRunAs() throws Exception { User user = new User("testUser"); - SecurityUserSubject subject = new SecurityUserSubject(threadPool, user); + SecurityUser subject = new SecurityUser(threadPool, user); assertThat(subject.getPrincipal().getName(), equalTo(user.getName())); diff --git a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java index 0b98913ab8..48851c48b3 100644 --- a/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java +++ b/src/test/java/org/opensearch/security/identity/ContextProvidingPluginSubjectTests.java @@ -16,6 +16,7 @@ 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; @@ -52,7 +53,7 @@ public void testSecurityUserSubjectRunAs() throws Exception { assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); - SecurityUserSubjectTests.terminate(threadPool); + SecurityUserTests.terminate(threadPool); } @Test @@ -78,7 +79,7 @@ public void testPluginContextSwitcherRunAs() throws Exception { assertNull(threadPool.getThreadContext().getTransient(OPENDISTRO_SECURITY_USER)); - SecurityUserSubjectTests.terminate(threadPool); + SecurityUserTests.terminate(threadPool); } @Test From 05890d5b240e22032fe0125cb6e166a862a2e64b Mon Sep 17 00:00:00 2001 From: Craig Perkins Date: Mon, 14 Oct 2024 16:02:44 -0400 Subject: [PATCH 49/49] Create common method for getting SecurityRoles and move plugin specific logic into method Signed-off-by: Craig Perkins --- .../SystemIndexSearcherWrapper.java | 2 +- .../privileges/PrivilegesEvaluator.java | 30 +++++++++++-------- 2 files changed, 19 insertions(+), 13 deletions(-) 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/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index 195f819f7b..1d904fc5bb 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -194,7 +194,22 @@ 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); } @@ -214,7 +229,7 @@ public boolean hasRestAdminPermissions(final User user, final TransportAddress r } private boolean hasRestAdminPermissions(final Set roles, String permission) { - final SecurityRoles securityRoles = getSecurityRoles(roles); + final SecurityRoles securityRoles = filterSecurityRolesFromCache(roles); return securityRoles.hasExplicitClusterPermissionPermission(permission); } @@ -294,16 +309,7 @@ public PrivilegesEvaluatorResponse evaluate(PrivilegesEvaluationContext context) context.setMappedRoles(mappedRoles); } presponse.resolvedSecurityRoles.addAll(mappedRoles); - final SecurityRoles securityRoles; - if (user.isPluginUser()) { - securityRoles = getSecurityRoleForPlugin(user.getName()); - } else { - 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) {