diff --git a/CHANGELOG.md b/CHANGELOG.md index 803a09b10a..e9fe78b38b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * Provide SecureHttpTransportParameters to complement SecureTransportParameters counterpart ([#5432](https://github.com/opensearch-project/security/pull/5432)) * Use isClusterPerm instead of requestedResolved.isLocalAll() to determine if action is a cluster action ([#5445](https://github.com/opensearch-project/security/pull/5445)) * Fix config update with deprecated config types failing in mixed clusters ([#5456](https://github.com/opensearch-project/security/pull/5456)) +* Add serialized user custom attributes to the the thread context ([#5491](https://github.com/opensearch-project/security/pull/5491)) ### Refactoring diff --git a/sample-resource-plugin/build.gradle b/sample-resource-plugin/build.gradle index 9ae6afba2d..f8b0c79b93 100644 --- a/sample-resource-plugin/build.gradle +++ b/sample-resource-plugin/build.gradle @@ -38,7 +38,7 @@ ext { licenseFile = rootProject.file('LICENSE.txt') noticeFile = rootProject.file('NOTICE.txt') - common_utils_version = System.getProperty("common_utils.version", "3.1.0.0") + common_utils_version = System.getProperty("common_utils.version", "3.2.0.0-SNAPSHOT") } repositories { @@ -71,6 +71,7 @@ dependencies { integrationTestImplementation rootProject.sourceSets.integrationTest.output integrationTestImplementation rootProject.sourceSets.main.output integrationTestImplementation "org.opensearch.client:opensearch-rest-high-level-client:${opensearch_version}" + integrationTestImplementation 'org.ldaptive:ldaptive:1.2.3' // To be removed once integration test framework supports extended plugins integrationTestImplementation project(path: ":${rootProject.name}-spi", configuration: 'shadow') diff --git a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java index b3d74ae0ef..3500346871 100644 --- a/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java +++ b/sample-resource-plugin/src/integrationTest/java/org/opensearch/sample/SampleResourcePluginTests.java @@ -77,7 +77,16 @@ public class SampleResourcePluginTests { .anonymousAuth(true) .authc(AUTHC_HTTPBASIC_INTERNAL) .users(USER_ADMIN, SHARED_WITH_USER) - .nodeSettings(Map.of(SECURITY_SYSTEM_INDICES_ENABLED_KEY, true, OPENSEARCH_RESOURCE_SHARING_ENABLED, true)) + .nodeSettings( + Map.of( + SECURITY_SYSTEM_INDICES_ENABLED_KEY, + true, + OPENSEARCH_RESOURCE_SHARING_ENABLED, + true, + "plugins.security.user_attribute_serialization.enabled", + true + ) + ) .build(); @After @@ -97,6 +106,34 @@ public void testPluginInstalledCorrectly() { } } + @Test + public void testUserSerializationAndDeserialization() throws Exception { + String resourceId; + // create sample resource + try (TestRestClient client = cluster.getRestClient(USER_ADMIN)) { + String sampleResource = """ + {"name":"sample"} + """; + + TestRestClient.HttpResponse response = client.putJson(SAMPLE_RESOURCE_CREATE_ENDPOINT, sampleResource); + response.assertStatusCode(HttpStatus.SC_OK); + + resourceId = response.getTextFromJsonBody("/message").split(":")[1].trim(); + + Awaitility.await() + .alias("Wait until resource data is populated") + .until(() -> client.get(SAMPLE_RESOURCE_GET_ENDPOINT + "/" + resourceId).getStatusCode(), equalTo(200)); + } + + try (TestRestClient client = cluster.getRestClient(cluster.getAdminCertificate())) { + Awaitility.await() + .alias("Wait until resource-sharing data is populated") + .until(() -> client.get(RESOURCE_SHARING_INDEX + "/_search").bodyAsJsonNode().get("hits").get("hits").size(), equalTo(1)); + HttpResponse sharingEntry = client.get(RESOURCE_SHARING_INDEX + "/_doc/" + resourceId); + System.out.println("sharingEntry: " + sharingEntry.getBody()); + } + } + @Test public void testCreateUpdateDeleteSampleResource() throws Exception { String resourceId; diff --git a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/CreateResourceTransportAction.java b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/CreateResourceTransportAction.java index e3de36f7cd..946336bb39 100644 --- a/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/CreateResourceTransportAction.java +++ b/sample-resource-plugin/src/main/java/org/opensearch/sample/resource/actions/transport/CreateResourceTransportAction.java @@ -56,6 +56,7 @@ protected void doExecute(Task task, CreateResourceRequest request, ActionListene ThreadContext threadContext = transportService.getThreadPool().getThreadContext(); String userStr = threadContext.getTransient(ConfigConstants.OPENSEARCH_SECURITY_USER_INFO_THREAD_CONTEXT); User user = User.parse(userStr); + System.out.println("Parsed User: " + user); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { createResource(request, user, listener); } catch (Exception e) { diff --git a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index 57aac53b6f..ea93ca1061 100644 --- a/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java +++ b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java @@ -449,9 +449,8 @@ public int hashCode() { public static final class User implements UserCredentialsHolder, ToXContentObject { - public final static TestSecurityConfig.User USER_ADMIN = new User("admin").roles( - new Role("allaccess").indexPermissions("*").on("*").clusterPermissions("*") - ); + public final static TestSecurityConfig.User USER_ADMIN = new User("admin").attr("attr1", "val1") + .roles(new Role("allaccess").indexPermissions("*").on("*").clusterPermissions("*")); String name; private String password; diff --git a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java index 04ef048965..46e14ff289 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -2222,6 +2222,15 @@ public List> getSettings() { Property.Final ) ); + + settings.add( + Setting.boolSetting( + ConfigConstants.USER_ATTRIBUTE_SERIALIZATION_ENABLED, + ConfigConstants.USER_ATTRIBUTE_SERIALIZATION_ENABLED_DEFAULT, + Property.NodeScope, + Property.Filtered + ) + ); } return settings; diff --git a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java index af5c3c3124..2f586d090c 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -103,6 +103,7 @@ import org.opensearch.security.securityconf.impl.v7.ActionGroupsV7; import org.opensearch.security.securityconf.impl.v7.RoleV7; import org.opensearch.security.securityconf.impl.v7.TenantV7; +import org.opensearch.security.support.Base64Helper; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.WildcardMatcher; import org.opensearch.security.user.User; @@ -113,6 +114,8 @@ import static org.opensearch.security.OpenSearchSecurityPlugin.traceAction; import static org.opensearch.security.support.ConfigConstants.OPENDISTRO_SECURITY_USER_INFO_THREAD_CONTEXT; +import static org.opensearch.security.support.ConfigConstants.USER_ATTRIBUTE_SERIALIZATION_ENABLED; +import static org.opensearch.security.support.ConfigConstants.USER_ATTRIBUTE_SERIALIZATION_ENABLED_DEFAULT; import static org.opensearch.security.support.SecurityUtils.escapePipe; public class PrivilegesEvaluator { @@ -278,6 +281,10 @@ public boolean isInitialized() { return configModel != null && dcm != null && actionPrivileges.get() != null; } + private boolean isUserAttributeSerializationEnabled() { + return this.settings.getAsBoolean(USER_ATTRIBUTE_SERIALIZATION_ENABLED, USER_ATTRIBUTE_SERIALIZATION_ENABLED_DEFAULT); + } + private void setUserInfoInThreadContext(User user, Set mappedRoles) { if (threadContext.getTransient(OPENDISTRO_SECURITY_USER_INFO_THREAD_CONTEXT) == null) { StringJoiner joiner = new StringJoiner("|"); @@ -289,7 +296,14 @@ private void setUserInfoInThreadContext(User user, Set mappedRoles) { String requestedTenant = user.getRequestedTenant(); if (!Strings.isNullOrEmpty(requestedTenant)) { joiner.add(escapePipe(requestedTenant)); + } else { + joiner.add("null"); } + + if (this.isUserAttributeSerializationEnabled()) { + joiner.add(Base64Helper.serializeObject(user.getCustomAttributesMap())); + } + threadContext.putTransient(OPENDISTRO_SECURITY_USER_INFO_THREAD_CONTEXT, joiner.toString()); } } diff --git a/src/main/java/org/opensearch/security/support/ConfigConstants.java b/src/main/java/org/opensearch/security/support/ConfigConstants.java index 2f542724b9..374549584b 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -404,6 +404,9 @@ public enum RolesMappingResolution { public static final String SECURITY_CONFIG_VERSION_RETENTION_COUNT = SECURITY_SETTINGS_PREFIX + "config_version.retention_count"; public static final int SECURITY_CONFIG_VERSION_RETENTION_COUNT_DEFAULT = 10; + public static final String USER_ATTRIBUTE_SERIALIZATION_ENABLED = SECURITY_SETTINGS_PREFIX + "user_attribute_serialization.enabled"; + public static final boolean USER_ATTRIBUTE_SERIALIZATION_ENABLED_DEFAULT = false; + // On-behalf-of endpoints settings // CS-SUPPRESS-SINGLE: RegexpSingleline get Extensions Settings public static final String EXTENSIONS_BWC_PLUGIN_MODE = "bwcPluginMode"; diff --git a/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java b/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java index e3ac97ddb7..20ae8d112a 100644 --- a/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java +++ b/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java @@ -16,12 +16,13 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Collection; -import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Pattern; +import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.amazon.dlic.auth.ldap.LdapUser; @@ -58,10 +59,15 @@ public final class SafeSerializationUtils { Number.class, Collection.class, Map.class, - Enum.class + Enum.class, + ImmutableMap.class ); - private static final Set SAFE_CLASS_NAMES = Collections.singleton("org.ldaptive.LdapAttribute$LdapAttributeValues"); + private static final Set SAFE_CLASS_NAMES = Set.of( + "org.ldaptive.LdapAttribute$LdapAttributeValues", + "com.google.common.collect.ImmutableBiMap$SerializedForm", + "com.google.common.collect.ImmutableMap$SerializedForm" + ); static final Map, Boolean> safeClassCache = new ConcurrentHashMap<>(); static boolean isSafeClass(Class cls) {