Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased 3.x]

### Changed
- Ensure all restHeaders from ActionPlugin.getRestHeaders are carried to threadContext for tracing ([#5396](https://github.com/opensearch-project/security/pull/5396))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation for this change? Without this how hard is it be debug?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the issue as described here: #4799

WLM (workload management) found this issue bc they have logic to tag queries and assign them to groups. It was noticed that after overridding ActionPlugin.getRestHeaders() the the headers they expected to trace around with the entire request handling process (i.e. from rest down to transport and carried node-to-node) were getting dropped bc of logic in the security repo to control what headers are sent internally. Not all HTTP Headers should be copied to the threadcontext and carried around, but the one's explicitly allowed via ActionPlugin.getRestHeaders() should be.

### Features

* Introduced new experimental versioned security configuration management feature ([#5357] (https://github.com/opensearch-project/security/pull/5357))
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ public void testAuditLogSimilarityWithTransportLayer() {
assertThat(client.get("_cat/indices").getStatusCode(), equalTo(HttpStatus.SC_OK));

// transport layer audit messages
auditLogsRule.assertExactly(1, grantedPrivilege(AUDIT_LOG_VERIFIER, "GetSettingsRequest"));
auditLogsRule.assertAtLeast(1, grantedPrivilege(AUDIT_LOG_VERIFIER, "GetSettingsRequest"));

List<AuditMessage> grantedPrivilegesMessages = auditLogsRule.getCurrentTestAuditMessages()
.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.apache.commons.collections4.ListUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.awaitility.Awaitility;
Expand Down Expand Up @@ -66,9 +67,7 @@ public void assertExactlyOne(Predicate<AuditMessage> predicate) {
}

public void assertExactlyScanAll(long expectedNumberOfAuditMessages, Predicate<AuditMessage> predicate) {
List<AuditMessage> auditMessages = new ArrayList<>(currentTestAuditMessages);
auditMessages.addAll(currentTransportTestAuditMessages);
assertExactly(exactNumberOfAuditsFulfillPredicate(expectedNumberOfAuditMessages, predicate), auditMessages);
assertExactly(exactNumberOfAuditsFulfillPredicate(expectedNumberOfAuditMessages, predicate));

}

Expand All @@ -79,23 +78,23 @@ public void assertAuditLogsCount(int from, int to) {
}

public void assertExactly(long expectedNumberOfAuditMessages, Predicate<AuditMessage> predicate) {
assertExactly(exactNumberOfAuditsFulfillPredicate(expectedNumberOfAuditMessages, predicate), currentTestAuditMessages);
assertExactly(exactNumberOfAuditsFulfillPredicate(expectedNumberOfAuditMessages, predicate));
}

private void assertExactly(Matcher<List<AuditMessage>> matcher, List<AuditMessage> currentTestAuditMessages) {
private void assertExactly(Matcher<List<AuditMessage>> matcher) {
// pollDelay - initial delay before first evaluation
Awaitility.await("Await for audit logs")
.atMost(3, TimeUnit.SECONDS)
.pollDelay(0, TimeUnit.MICROSECONDS)
.until(() -> new ArrayList<>(currentTestAuditMessages), matcher);
.until(() -> ListUtils.union(currentTestAuditMessages, currentTransportTestAuditMessages), matcher);
}

public void assertAtLeast(long minCount, Predicate<AuditMessage> predicate) {
assertExactly(atLeastCertainNumberOfAuditsFulfillPredicate(minCount, predicate), currentTestAuditMessages);
assertExactly(atLeastCertainNumberOfAuditsFulfillPredicate(minCount, predicate));
}

public void assertAtLeastTransportMessages(long minCount, Predicate<AuditMessage> predicate) {
assertExactly(atLeastCertainNumberOfAuditsFulfillPredicate(minCount, predicate), currentTransportTestAuditMessages);
assertExactly(atLeastCertainNumberOfAuditsFulfillPredicate(minCount, predicate));
}

private static String auditMessagesToString(List<AuditMessage> audits) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import java.nio.file.Path;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;
Expand Down Expand Up @@ -63,7 +64,6 @@
import org.opensearch.security.support.ConfigConstants;
import org.opensearch.security.support.HTTPHelper;
import org.opensearch.security.user.User;
import org.opensearch.tasks.Task;
import org.opensearch.threadpool.ThreadPool;
import org.opensearch.transport.client.node.NodeClient;

Expand All @@ -72,6 +72,7 @@
import static org.opensearch.security.OpenSearchSecurityPlugin.LEGACY_OPENDISTRO_PREFIX;
import static org.opensearch.security.OpenSearchSecurityPlugin.PLUGINS_PREFIX;
import static org.opensearch.security.support.ConfigConstants.OPENDISTRO_SECURITY_INITIATING_USER;
import static org.opensearch.security.support.ConfigConstants.OPENSEARCH_SECURITY_REQUEST_HEADERS;

public class SecurityRestFilter {

Expand Down Expand Up @@ -138,10 +139,13 @@ public void handleRequest(RestRequest request, RestChannel channel, NodeClient c

NettyAttribute.popFrom(request, Netty4HttpRequestHeaderVerifier.CONTEXT_TO_RESTORE).ifPresent(storedContext -> {
// X_OPAQUE_ID will be overritten on restore - save to apply after restoring the saved context
final String xOpaqueId = threadContext.getHeader(Task.X_OPAQUE_ID);
final Map<String, String> tmpHeaders = threadContext.getHeaders();
storedContext.restore();
if (xOpaqueId != null) {
threadContext.putHeader(Task.X_OPAQUE_ID, xOpaqueId);
for (Map.Entry<String, String> header : tmpHeaders.entrySet()) {
threadContext.putHeader(header.getKey(), header.getValue());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

qq, will values be null for any header?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no, null is not possible. There could be empty

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this need specific headers or all headers? Copying/populating all headers might be costly?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 Could this cause perf regression?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the linked issue: #4799

There is a bug with ActionPlugin.getRestHeaders() where the headers are getting dropped because the security plugin is not carrying them from rest -> transport and then also propagating them internally in the transport layer.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't think this would have a high performance penalty.

}
if (!tmpHeaders.isEmpty()) {
threadContext.putHeader(OPENSEARCH_SECURITY_REQUEST_HEADERS, String.join(",", tmpHeaders.keySet()));
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
public class ConfigConstants {

public static final String OPENDISTRO_SECURITY_CONFIG_PREFIX = "_opendistro_security_";
public static final String OPENSEARCH_SECURITY_CONFIG_PREFIX = "_opensearch_security_";
public static final String SECURITY_SETTINGS_PREFIX = "plugins.security.";

public static final String OPENSEARCH_SECURITY_DISABLED = SECURITY_SETTINGS_PREFIX + "disabled";
Expand Down Expand Up @@ -79,6 +80,7 @@ public class ConfigConstants {
public static final String OPENDISTRO_SECURITY_MASKED_FIELD_CCS = OPENDISTRO_SECURITY_CONFIG_PREFIX + "masked_fields_ccs";

public static final String OPENDISTRO_SECURITY_CONF_REQUEST_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX + "conf_request";
public static final String OPENSEARCH_SECURITY_REQUEST_HEADERS = OPENSEARCH_SECURITY_CONFIG_PREFIX + "request_headers";

public static final String OPENDISTRO_SECURITY_REMOTE_ADDRESS = OPENDISTRO_SECURITY_CONFIG_PREFIX + "remote_address";
public static final String OPENDISTRO_SECURITY_REMOTE_ADDRESS_HEADER = OPENDISTRO_SECURITY_CONFIG_PREFIX + "remote_address_header";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@
package org.opensearch.security.transport;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.function.Supplier;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -64,6 +67,7 @@
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;
import org.opensearch.transport.Transport.Connection;
import org.opensearch.transport.TransportException;
Expand Down Expand Up @@ -158,6 +162,14 @@ public <T extends TransportResponse> void sendRequestDecorate(
final boolean isDebugEnabled = log.isDebugEnabled();

final boolean isSameNodeRequest = localNode != null && localNode.equals(connection.getNode());
final Set<String> requestHeadersToCopy = new HashSet<>();
if (getThreadContext().getHeader(ConfigConstants.OPENSEARCH_SECURITY_REQUEST_HEADERS) != null) {
Collections.addAll(
requestHeadersToCopy,
getThreadContext().getHeader(ConfigConstants.OPENSEARCH_SECURITY_REQUEST_HEADERS).split(",")
);
requestHeadersToCopy.remove(Task.X_OPAQUE_ID); // Special case where this header is preserved during stashContext.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes in this file are necessary because currently only X_OPAQUE_ID is not lost when stashing here: https://github.com/opensearch-project/OpenSearch/blob/main/server/src/main/java/org/opensearch/common/util/concurrent/ThreadContext.java#L157-L163

Ideally, this is solved another way through the core. Either:

  1. Change X_OPAQUE_ID to a persistent header and remove the logic in stashContext. persistentHeaders are a special category of headers that are not stashable.
  2. Update the logic in ThreadContext to not stash any of the headers from https://github.com/opensearch-project/OpenSearch/blob/main/server/src/main/java/org/opensearch/action/ActionModule.java#L581-L586

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, option 1 is better.

}

try (ThreadContext.StoredContext stashedContext = getThreadContext().stashContext()) {
final TransportResponseHandler<T> restoringHandler = new RestoringTransportResponseHandler<T>(handler, stashedContext);
Expand All @@ -178,11 +190,13 @@ public <T extends TransportResponse> void sendRequestDecorate(
|| k.equals(ConfigConstants.OPENDISTRO_SECURITY_FILTER_LEVEL_DLS_DONE)
|| k.equals(ConfigConstants.OPENDISTRO_SECURITY_DLS_MODE_HEADER)
|| k.equals(ConfigConstants.OPENDISTRO_SECURITY_DLS_FILTER_LEVEL_QUERY_HEADER)
|| k.equals(ConfigConstants.OPENSEARCH_SECURITY_REQUEST_HEADERS)
|| (k.equals("_opendistro_security_source_field_context")
&& !(request instanceof SearchRequest)
&& !(request instanceof GetRequest))
|| k.startsWith("_opendistro_security_trace")
|| k.startsWith(ConfigConstants.OPENDISTRO_SECURITY_INITIAL_ACTION_CLASS_HEADER))
|| k.startsWith(ConfigConstants.OPENDISTRO_SECURITY_INITIAL_ACTION_CLASS_HEADER)
|| requestHeadersToCopy.contains(k))
)
);

Expand Down
Loading