diff --git a/CHANGELOG.md b/CHANGELOG.md index 30ed7527a1..1d504b484d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * Fix usage of jwt_clock_skew_tolerance_seconds in HTTPJwtAuthenticator ([#5506](https://github.com/opensearch-project/security/pull/5506)) * Always install demo certs if configured with demo certs ([#5517](https://github.com/opensearch-project/security/pull/5517)) * [Resource Sharing] Restores client accessor pattern to fix compilation issues when security plugin is not installed ([#5541](https://github.com/opensearch-project/security/pull/5541)) +* 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 5e2b2bb95a..39fd7a314e 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 { @@ -80,6 +80,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/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java b/src/integrationTest/java/org/opensearch/test/framework/TestSecurityConfig.java index 31e8cf891a..d58070ab45 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 10e5dbd0ea..4aec5bd204 100644 --- a/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java +++ b/src/main/java/org/opensearch/security/OpenSearchSecurityPlugin.java @@ -2230,6 +2230,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 0c8568c7c1..65c98d7165 100644 --- a/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java +++ b/src/main/java/org/opensearch/security/privileges/PrivilegesEvaluator.java @@ -26,6 +26,7 @@ package org.opensearch.security.privileges; +import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -103,6 +104,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 +115,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 { @@ -283,6 +287,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(PrivilegesEvaluationContext context) { if (threadContext.getTransient(OPENDISTRO_SECURITY_USER_INFO_THREAD_CONTEXT) == null) { StringJoiner joiner = new StringJoiner("|"); @@ -293,9 +301,15 @@ private void setUserInfoInThreadContext(PrivilegesEvaluationContext context) { String requestedTenant = context.getUser().getRequestedTenant(); joiner.add(requestedTenant); + String tenantAccessToCheck = getTenancyAccess(context); joiner.add(tenantAccessToCheck); log.debug(joiner); + + if (this.isUserAttributeSerializationEnabled()) { + joiner.add(Base64Helper.serializeObject((Serializable) context.getUser().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 af6b8380ea..1f9ac97090 100644 --- a/src/main/java/org/opensearch/security/support/ConfigConstants.java +++ b/src/main/java/org/opensearch/security/support/ConfigConstants.java @@ -409,6 +409,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..be4bbc05c6 100644 --- a/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java +++ b/src/main/java/org/opensearch/security/support/SafeSerializationUtils.java @@ -16,12 +16,12 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Collection; -import java.util.Collections; 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 +58,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) {