diff --git a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java index b46b5230b0..793ce93427 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java +++ b/src/main/java/org/opensearch/security/transport/SecurityInterceptor.java @@ -53,20 +53,16 @@ import org.opensearch.common.settings.Settings; import org.opensearch.common.util.concurrent.ThreadContext; import org.opensearch.core.common.io.stream.StreamInput; -import org.opensearch.core.common.transport.TransportAddress; import org.opensearch.core.transport.TransportResponse; import org.opensearch.security.OpenSearchSecurityPlugin; import org.opensearch.security.auditlog.AuditLog; -import org.opensearch.security.auditlog.AuditLog.Origin; import org.opensearch.security.auth.BackendRegistry; import org.opensearch.security.configuration.ClusterInfoHolder; import org.opensearch.security.privileges.dlsfls.DlsFlsLegacyHeaders; import org.opensearch.security.ssl.SslExceptionHandler; import org.opensearch.security.ssl.transport.PrincipalExtractor; import org.opensearch.security.ssl.transport.SSLConfig; -import org.opensearch.security.support.Base64Helper; import org.opensearch.security.support.ConfigConstants; -import org.opensearch.security.user.User; import org.opensearch.security.user.UserFactory; import org.opensearch.tasks.Task; import org.opensearch.threadpool.ThreadPool; @@ -152,21 +148,15 @@ public void sendRequestDecorate( DiscoveryNode localNode ) { final Map origHeaders0 = getThreadContext().getHeaders(); - final User user0 = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); - final String injectedUserString = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER); - final String injectedRolesString = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES); + final TransportIdentityContext identityContext = TransportIdentityContext.capture(getThreadContext()); final String injectedRolesValidationString = getThreadContext().getTransient( ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION ); - final String origin0 = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN); - final Object remoteAddress0 = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); final String origCCSTransientDls = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_DLS_QUERY_CCS); final String origCCSTransientFls = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_FLS_FIELDS_CCS); final String origCCSTransientMf = getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_MASKED_FIELD_CCS); final DlsFlsLegacyHeaders dlsFlsLegacyHeaders = getThreadContext().getTransient(DlsFlsLegacyHeaders.TRANSIENT_HEADER); - final User authenticatedUser = (User) getThreadContext().getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER); - final boolean isDebugEnabled = log.isDebugEnabled(); final boolean isStreamChannel = options != null && TransportRequestOptions.Type.STREAM.equals(options.type()); // skip the same node optimization for stream transport which doesn't use DirectChannel and thus ser/de is needed @@ -280,15 +270,7 @@ && getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROL getThreadContext().putHeader(headerMap); - ensureCorrectHeaders( - remoteAddress0, - user0, - authenticatedUser, - origin0, - injectedUserString, - injectedRolesString, - isSameNodeRequest - ); + identityContext.propagate(getThreadContext(), isSameNodeRequest); if (actionTraceEnabled.get()) { getThreadContext().putHeader( @@ -313,92 +295,6 @@ boolean isCrossClusterSearchEnabled() { return OpenSearchSecurityPlugin.GuiceHolder.getRemoteClusterService().isCrossClusterSearchEnabled(); } - private void ensureCorrectHeaders( - final Object remoteAdr, - final User origUser, - final User authenticatedUser, - final String origin, - final String injectedUserString, - final String injectedRolesString, - final boolean isSameNodeRequest - ) { - // keep original address - - if (origin != null - && !origin.isEmpty() /*&& !Origin.LOCAL.toString().equalsIgnoreCase(origin)*/ - && getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER) == null) { - getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER, origin); - } - - if (origin == null && getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER) == null) { - getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER, Origin.LOCAL.toString()); - } - - TransportAddress transportAddress = null; - if (remoteAdr != null && remoteAdr instanceof TransportAddress) { - String remoteAddressHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER); - if (remoteAddressHeader == null) { - transportAddress = (TransportAddress) remoteAdr; - } - } - - // we put headers as transient for same node requests - if (isSameNodeRequest) { - if (transportAddress != null) { - getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, transportAddress); - } - - 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. - getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, origUser); - } else if (StringUtils.isNotEmpty(injectedRolesString)) { - getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, injectedRolesString); - } else if (StringUtils.isNotEmpty(injectedUserString)) { - getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, injectedUserString); - } - } else { - if (transportAddress != null) { - getThreadContext().putHeader( - ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER, - Base64Helper.serializeObject(transportAddress.address()) - ); - } - - // Propagate the authenticated user so it can be restored when the request is received. - String authenticatedUserHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER_HEADER); - String userSameAsAuthenticatedUserHeader = getThreadContext().getHeader( - ConfigConstants.OPENDISTRO_SECURITY_USER_SAME_AS_SUBJECT_HEADER - ); - if (authenticatedUserHeader == null && authenticatedUser != null) { - if (origUser != null && origUser.equals(authenticatedUser)) { - if (userSameAsAuthenticatedUserHeader == null) { - getThreadContext().putHeader( - ConfigConstants.OPENDISTRO_SECURITY_USER_SAME_AS_SUBJECT_HEADER, - Boolean.TRUE.toString() - ); - } - } else { - getThreadContext().putHeader( - ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER_HEADER, - authenticatedUser.toSerializedBase64() - ); - } - } - final String userHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER); - if (userHeader == null) { - // put as headers for other requests - if (origUser != null) { - getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER, origUser.toSerializedBase64()); - } else if (StringUtils.isNotEmpty(injectedRolesString)) { - getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_HEADER, injectedRolesString); - } else if (StringUtils.isNotEmpty(injectedUserString)) { - getThreadContext().putHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER_HEADER, injectedUserString); - } - } - } - } - private ThreadContext getThreadContext() { return threadPool.getThreadContext(); } diff --git a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java index 9ee96915f1..a5caa78c59 100644 --- a/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java +++ b/src/main/java/org/opensearch/security/transport/SecurityRequestHandler.java @@ -26,7 +26,6 @@ package org.opensearch.security.transport; -import java.net.InetSocketAddress; import java.security.cert.X509Certificate; import java.util.List; import java.util.UUID; @@ -40,7 +39,6 @@ import org.opensearch.action.support.replication.TransportReplicationAction.ConcreteShardRequest; import org.opensearch.cluster.service.ClusterService; import org.opensearch.common.util.concurrent.ThreadContext; -import org.opensearch.core.common.transport.TransportAddress; import org.opensearch.extensions.ExtensionsManager; import org.opensearch.search.internal.ShardSearchRequest; import org.opensearch.security.DefaultObjectMapper; @@ -53,10 +51,8 @@ import org.opensearch.security.ssl.transport.SSLConfig; import org.opensearch.security.ssl.transport.SecuritySSLRequestHandler; import org.opensearch.security.ssl.util.ExceptionUtils; -import org.opensearch.security.support.Base64Helper; import org.opensearch.security.support.ConfigConstants; import org.opensearch.security.support.HeaderHelper; -import org.opensearch.security.user.User; import org.opensearch.security.user.UserFactory; import org.opensearch.security.util.ParentChildrenQueryDetector; import org.opensearch.tasks.Task; @@ -121,11 +117,7 @@ protected void messageReceivedDecorate( final ThreadContext.StoredContext sgContext = getThreadContext().newStoredContext(false); - final String originHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER); - - if (!Strings.isNullOrEmpty(originHeader)) { - getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, originHeader); - } + TransportIdentityContext.restoreOrigin(getThreadContext()); // restore headers used for DLS final var dlsRequestHeadersAsString = getThreadContext().getHeader(ConfigConstants.OPENSEARCH_SECURITY_DLS_REQUEST_HEADERS); @@ -162,17 +154,8 @@ protected void messageReceivedDecorate( } // bypass non-netty requests - if (getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER) != null - || getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER) != null - || getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES) != null - || getThreadContext().getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS) != null) { - - final String rolesValidation = getThreadContext().getHeader( - ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION_HEADER - ); - if (!Strings.isNullOrEmpty(rolesValidation)) { - getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION, rolesValidation); - } + if (TransportIdentityContext.hasTransientIdentity(getThreadContext())) { + TransportIdentityContext.restoreRolesValidation(getThreadContext()); if (isActionTraceEnabled()) { getThreadContext().putHeader( @@ -187,68 +170,12 @@ protected void messageReceivedDecorate( putInitialActionClassHeader(initialActionClassValue, resolvedActionClass); } else { - String authenticatedUserHeader = getThreadContext().getHeader( - ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER_HEADER - ); - String userSameAsAuthenticatedUserHeader = getThreadContext().getHeader( - ConfigConstants.OPENDISTRO_SECURITY_USER_SAME_AS_SUBJECT_HEADER - ); - String userHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER); - - // Deserialize and sanitize users. - User user = null; - if (userHeader != null) { - user = this.userFactory.fromSerializedBase64(userHeader); - user = remoteClusterIdentityPolicy.sanitize(user, getThreadContext()); - } - User authenticatedUser = null; - if (authenticatedUserHeader != null) { - authenticatedUser = this.userFactory.fromSerializedBase64(authenticatedUserHeader); - authenticatedUser = remoteClusterIdentityPolicy.sanitize(authenticatedUser, getThreadContext()); - } - - // Store the authenticated user in persistent context (if not already set). - if (getThreadContext().getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER) == null) { - if (Boolean.parseBoolean(userSameAsAuthenticatedUserHeader) && user != null) { - getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, user); - } else if (authenticatedUser != null) { - getThreadContext().putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, authenticatedUser); - } - } - - // Store transient user or injected roles - final String injectedRolesHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_HEADER); - final String injectedUserHeader = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER_HEADER); - - if (Strings.isNullOrEmpty(userHeader)) { - // Keeping role injection with higher priority as plugins under OpenSearch will be using this - // on transport layer - if (!Strings.isNullOrEmpty(injectedRolesHeader)) { - getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, injectedRolesHeader); - } else if (!Strings.isNullOrEmpty(injectedUserHeader)) { - getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, injectedUserHeader); - } - } else { - getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); - } - - String originalRemoteAddress = getThreadContext().getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER); - - if (!Strings.isNullOrEmpty(originalRemoteAddress)) { - getThreadContext().putTransient( - ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, - new TransportAddress((InetSocketAddress) Base64Helper.deserializeObject(originalRemoteAddress)) - ); - } else { - getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, request.remoteAddress()); - } - - final String rolesValidation = getThreadContext().getHeader( - ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION_HEADER + TransportIdentityContext.restoreSerializedIdentity( + getThreadContext(), + request.remoteAddress(), + userFactory, + remoteClusterIdentityPolicy ); - if (!Strings.isNullOrEmpty(rolesValidation)) { - getThreadContext().putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION, rolesValidation); - } } if (channelType.equals("direct")) { diff --git a/src/main/java/org/opensearch/security/transport/TransportIdentityContext.java b/src/main/java/org/opensearch/security/transport/TransportIdentityContext.java new file mode 100644 index 0000000000..ab74777660 --- /dev/null +++ b/src/main/java/org/opensearch/security/transport/TransportIdentityContext.java @@ -0,0 +1,213 @@ +/* + * 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.transport; + +import java.net.InetSocketAddress; + +import com.google.common.base.Strings; +import org.apache.commons.lang3.StringUtils; + +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.common.transport.TransportAddress; +import org.opensearch.security.auditlog.AuditLog.Origin; +import org.opensearch.security.support.Base64Helper; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; +import org.opensearch.security.user.UserFactory; + +final class TransportIdentityContext { + + private final User user; + private final User authenticatedUser; + private final String injectedUser; + private final String injectedRoles; + private final String origin; + private final TransportAddress remoteAddress; + + private TransportIdentityContext( + User user, + User authenticatedUser, + String injectedUser, + String injectedRoles, + String origin, + TransportAddress remoteAddress + ) { + this.user = user; + this.authenticatedUser = authenticatedUser; + this.injectedUser = injectedUser; + this.injectedRoles = injectedRoles; + this.origin = origin; + this.remoteAddress = remoteAddress; + } + + static TransportIdentityContext capture(ThreadContext threadContext) { + final Object remoteAddress = threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS); + return new TransportIdentityContext( + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER), + (User) threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER), + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER), + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES), + threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN), + remoteAddress instanceof TransportAddress transportAddress ? transportAddress : null + ); + } + + void propagate(ThreadContext threadContext, boolean isSameNodeRequest) { + propagateOrigin(threadContext); + final boolean hasRemoteAddressHeader = threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER) != null; + final TransportAddress addressToPropagate = hasRemoteAddressHeader ? null : remoteAddress; + if (isSameNodeRequest) { + propagateTransientIdentity(threadContext, addressToPropagate); + } else { + propagateSerializedIdentity(threadContext, addressToPropagate); + } + } + + static boolean hasTransientIdentity(ThreadContext threadContext) { + return threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER) != null + || threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER) != null + || threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES) != null + || threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS) != null; + } + + static void restoreOrigin(ThreadContext threadContext) { + final String originHeader = threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER); + if (!Strings.isNullOrEmpty(originHeader)) { + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, originHeader); + } + } + + static void restoreSerializedIdentity( + ThreadContext threadContext, + TransportAddress requestRemoteAddress, + UserFactory userFactory, + RemoteClusterIdentityPolicy remoteClusterIdentityPolicy + ) { + final String userHeader = threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER); + final String authenticatedUserHeader = threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER_HEADER); + final User user = deserializeUser(userHeader, threadContext, userFactory, remoteClusterIdentityPolicy); + final User authenticatedUser = deserializeUser(authenticatedUserHeader, threadContext, userFactory, remoteClusterIdentityPolicy); + + restoreAuthenticatedUser(threadContext, user, authenticatedUser); + restoreEffectiveIdentity(threadContext, userHeader, user); + restoreRemoteAddress(threadContext, requestRemoteAddress); + restoreRolesValidation(threadContext); + } + + static void restoreRolesValidation(ThreadContext threadContext) { + final String rolesValidation = threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION_HEADER); + if (!Strings.isNullOrEmpty(rolesValidation)) { + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_VALIDATION, rolesValidation); + } + } + + private void propagateOrigin(ThreadContext threadContext) { + if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER) != null) { + return; + } + if (origin == null) { + threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER, Origin.LOCAL.toString()); + } else if (!origin.isEmpty()) { + threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER, origin); + } + } + + private void propagateTransientIdentity(ThreadContext threadContext, TransportAddress addressToPropagate) { + if (addressToPropagate != null) { + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, addressToPropagate); + } + if (user != null) { + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); + } else if (StringUtils.isNotEmpty(injectedRoles)) { + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, injectedRoles); + } else if (StringUtils.isNotEmpty(injectedUser)) { + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, injectedUser); + } + } + + private void propagateSerializedIdentity(ThreadContext threadContext, TransportAddress addressToPropagate) { + if (addressToPropagate != null) { + threadContext.putHeader( + ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER, + Base64Helper.serializeObject(addressToPropagate.address()) + ); + } + propagateAuthenticatedUser(threadContext); + if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER) == null) { + if (user != null) { + threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER, user.toSerializedBase64()); + } else if (StringUtils.isNotEmpty(injectedRoles)) { + threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_HEADER, injectedRoles); + } else if (StringUtils.isNotEmpty(injectedUser)) { + threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER_HEADER, injectedUser); + } + } + } + + private void propagateAuthenticatedUser(ThreadContext threadContext) { + if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER_HEADER) != null || authenticatedUser == null) { + return; + } + if (authenticatedUser.equals(user)) { + if (threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_SAME_AS_SUBJECT_HEADER) == null) { + threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_SAME_AS_SUBJECT_HEADER, Boolean.TRUE.toString()); + } + } else { + threadContext.putHeader(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER_HEADER, authenticatedUser.toSerializedBase64()); + } + } + + private static User deserializeUser( + String header, + ThreadContext threadContext, + UserFactory userFactory, + RemoteClusterIdentityPolicy remoteClusterIdentityPolicy + ) { + if (header == null) { + return null; + } + return remoteClusterIdentityPolicy.sanitize(userFactory.fromSerializedBase64(header), threadContext); + } + + private static void restoreAuthenticatedUser(ThreadContext threadContext, User user, User authenticatedUser) { + if (threadContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER) != null) { + return; + } + final boolean userIsAuthenticatedUser = Boolean.parseBoolean( + threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_SAME_AS_SUBJECT_HEADER) + ); + if (userIsAuthenticatedUser && user != null) { + threadContext.putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, user); + } else if (authenticatedUser != null) { + threadContext.putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, authenticatedUser); + } + } + + private static void restoreEffectiveIdentity(ThreadContext threadContext, String userHeader, User user) { + if (!Strings.isNullOrEmpty(userHeader)) { + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); + return; + } + final String injectedRoles = threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_HEADER); + final String injectedUser = threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER_HEADER); + if (!Strings.isNullOrEmpty(injectedRoles)) { + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, injectedRoles); + } else if (!Strings.isNullOrEmpty(injectedUser)) { + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, injectedUser); + } + } + + private static void restoreRemoteAddress(ThreadContext threadContext, TransportAddress requestRemoteAddress) { + final String serializedAddress = threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER); + final TransportAddress remoteAddress = Strings.isNullOrEmpty(serializedAddress) + ? requestRemoteAddress + : new TransportAddress((InetSocketAddress) Base64Helper.deserializeObject(serializedAddress)); + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, remoteAddress); + } +} diff --git a/src/test/java/org/opensearch/security/transport/TransportIdentityContextTests.java b/src/test/java/org/opensearch/security/transport/TransportIdentityContextTests.java new file mode 100644 index 0000000000..73cd7e921f --- /dev/null +++ b/src/test/java/org/opensearch/security/transport/TransportIdentityContextTests.java @@ -0,0 +1,112 @@ +/* + * 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.transport; + +import java.util.Map; + +import org.junit.Test; + +import org.opensearch.common.settings.Settings; +import org.opensearch.common.util.concurrent.ThreadContext; +import org.opensearch.core.common.transport.TransportAddress; +import org.opensearch.security.auditlog.AuditLog.Origin; +import org.opensearch.security.support.ConfigConstants; +import org.opensearch.security.user.User; +import org.opensearch.security.user.UserFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class TransportIdentityContextTests { + + private static final TransportAddress REMOTE_ADDRESS = new TransportAddress(TransportAddress.META_ADDRESS, 9300); + + @Test + public void testPropagatesSameNodeIdentityAsTransients() { + final ThreadContext threadContext = new ThreadContext(Settings.EMPTY); + final User user = new User("test-user"); + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); + threadContext.putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, user); + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, REMOTE_ADDRESS); + threadContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN, Origin.TRANSPORT.toString()); + + final TransportIdentityContext identityContext = TransportIdentityContext.capture(threadContext); + try (ThreadContext.StoredContext ignored = threadContext.stashContext()) { + identityContext.propagate(threadContext, true); + + assertSame(user, threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER)); + assertSame(REMOTE_ADDRESS, threadContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS)); + assertEquals(Origin.TRANSPORT.toString(), threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN_HEADER)); + assertNull(threadContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_HEADER)); + } + } + + @Test + public void testRoundTripsSerializedIdentity() { + final ThreadContext senderContext = new ThreadContext(Settings.EMPTY); + final User user = new User("test-user"); + senderContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, user); + senderContext.putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, user); + senderContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS, REMOTE_ADDRESS); + + final TransportIdentityContext identityContext = TransportIdentityContext.capture(senderContext); + final Map headers; + try (ThreadContext.StoredContext ignored = senderContext.stashContext()) { + identityContext.propagate(senderContext, false); + headers = Map.copyOf(senderContext.getHeaders()); + } + + final ThreadContext receiverContext = new ThreadContext(Settings.EMPTY); + receiverContext.putHeader(headers); + TransportIdentityContext.restoreOrigin(receiverContext); + TransportIdentityContext.restoreSerializedIdentity( + receiverContext, + new TransportAddress(TransportAddress.META_ADDRESS, 9400), + new UserFactory.Simple(), + new RemoteClusterIdentityPolicy(false) + ); + + final User restoredUser = receiverContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_USER); + assertEquals(user, restoredUser); + assertSame(restoredUser, receiverContext.getPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER)); + assertEquals(REMOTE_ADDRESS, receiverContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_REMOTE_ADDRESS)); + assertEquals(Origin.LOCAL.toString(), receiverContext.getTransient(ConfigConstants.OPENDISTRO_SECURITY_ORIGIN)); + assertTrue(Boolean.parseBoolean(headers.get(ConfigConstants.OPENDISTRO_SECURITY_USER_SAME_AS_SUBJECT_HEADER))); + } + + @Test + public void testSerializesDistinctAuthenticatedUser() { + final ThreadContext senderContext = new ThreadContext(Settings.EMPTY); + senderContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_USER, new User("effective-user")); + senderContext.putPersistent(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER, new User("authenticated-user")); + + final TransportIdentityContext identityContext = TransportIdentityContext.capture(senderContext); + try (ThreadContext.StoredContext ignored = senderContext.stashContext()) { + identityContext.propagate(senderContext, false); + assertTrue(senderContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_AUTHENTICATED_USER_HEADER).length() > 0); + assertNull(senderContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_USER_SAME_AS_SUBJECT_HEADER)); + } + } + + @Test + public void testInjectedRolesTakePrecedenceOverInjectedUser() { + final ThreadContext senderContext = new ThreadContext(Settings.EMPTY); + senderContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES, "role-a,role-b"); + senderContext.putTransient(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER, "injected-user"); + + final TransportIdentityContext identityContext = TransportIdentityContext.capture(senderContext); + try (ThreadContext.StoredContext ignored = senderContext.stashContext()) { + identityContext.propagate(senderContext, false); + assertEquals("role-a,role-b", senderContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_ROLES_HEADER)); + assertNull(senderContext.getHeader(ConfigConstants.OPENDISTRO_SECURITY_INJECTED_USER_HEADER)); + } + } +}