Skip to content

Commit c77b0ea

Browse files
authored
Preserve request headers in a mixed version cluster (elastic#79412) (elastic#79441)
* Preserve request headers in a mixed version cluster (elastic#79412) When rewriting authentication for requests crossing nodes of different versions, we now preserve all request headers except the authentication one which needs to be rewritten. Previously all other request headers were dropped and it caused issue like an operator user not being recognised on the remote node. Other now preserved headers include audit and system index access. This new behaviour is more correct because we would never drop these headers if the nodes are on the same version. Resolves: elastic#79354 * for for 7.x quirks
1 parent b4b20bd commit c77b0ea

File tree

5 files changed

+42
-1
lines changed

5 files changed

+42
-1
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.elasticsearch.node.Node;
2222
import org.elasticsearch.xpack.core.security.authc.Authentication;
2323
import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType;
24+
import org.elasticsearch.xpack.core.security.authc.AuthenticationField;
2425
import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer;
2526
import org.elasticsearch.xpack.core.security.authc.support.SecondaryAuthentication;
2627
import org.elasticsearch.xpack.core.security.user.User;
@@ -157,12 +158,19 @@ public <T> T executeWithAuthentication(Authentication authentication, Function<S
157158
* The original context is provided to the consumer. When this method returns, the original context is restored.
158159
*/
159160
public void executeAfterRewritingAuthentication(Consumer<StoredContext> consumer, Version version) {
161+
// Preserve request headers other than authentication
162+
final Map<String, String> existingRequestHeaders = threadContext.getRequestHeadersOnly();
160163
final StoredContext original = threadContext.newStoredContext(true);
161164
final Authentication authentication = getAuthentication();
162165
try (ThreadContext.StoredContext ignore = threadContext.stashContext()) {
163166
setAuthentication(new Authentication(authentication.getUser(), authentication.getAuthenticatedBy(),
164167
authentication.getLookedUpBy(), version, authentication.getAuthenticationType(),
165168
rewriteMetadataForApiKeyRoleDescriptors(version, authentication)));
169+
existingRequestHeaders.forEach((k, v) -> {
170+
if (false == AuthenticationField.AUTHENTICATION_KEY.equals(k)) {
171+
threadContext.putHeader(k, v);
172+
}
173+
});
166174
consumer.accept(original);
167175
}
168176
}

x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityContextTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import static org.elasticsearch.xpack.core.security.authc.Authentication.VERSION_API_KEY_ROLES_AS_BYTES;
3434
import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY;
3535
import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY;
36+
import static org.hamcrest.Matchers.equalTo;
3637
import static org.hamcrest.Matchers.instanceOf;
3738

3839
public class SecurityContextTests extends ESTestCase {
@@ -119,6 +120,11 @@ public void testExecuteAfterRewritingAuthentication() throws IOException {
119120
RealmRef authBy = new RealmRef("ldap", "foo", "node1");
120121
final Authentication original = new Authentication(user, authBy, authBy);
121122
original.writeToContext(threadContext);
123+
final Map<String, String> requestHeaders = org.elasticsearch.core.Map.of(
124+
AuthenticationField.PRIVILEGE_CATEGORY_KEY, randomAlphaOfLengthBetween(3, 10),
125+
randomAlphaOfLengthBetween(3, 8), randomAlphaOfLengthBetween(3, 8)
126+
);
127+
threadContext.putHeader(requestHeaders);
122128

123129
final AtomicReference<StoredContext> contextAtomicReference = new AtomicReference<>();
124130
securityContext.executeAfterRewritingAuthentication(originalCtx -> {
@@ -129,6 +135,8 @@ public void testExecuteAfterRewritingAuthentication() throws IOException {
129135
assertEquals(VersionUtils.getPreviousVersion(), authentication.getVersion());
130136
assertEquals(original.getAuthenticationType(), securityContext.getAuthentication().getAuthenticationType());
131137
contextAtomicReference.set(originalCtx);
138+
// Other request headers should be preserved
139+
requestHeaders.forEach((k, v) -> assertThat(threadContext.getHeader(k), equalTo(v)));
132140
}, VersionUtils.getPreviousVersion());
133141

134142
final Authentication authAfterExecution = securityContext.getAuthentication();

x-pack/qa/rolling-upgrade/build.gradle

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@ for (Version bwcVersion : BuildParams.bwcVersions.wireCompatible) {
6767
if (bwcVersion.onOrAfter('6.6.0')) {
6868
setting 'ccr.auto_follow.wait_for_metadata_timeout', '1s'
6969
}
70+
if (bwcVersion.onOrAfter('7.11.0')) {
71+
extraConfigFile 'operator_users.yml', file("${project.projectDir}/src/test/resources/operator_users.yml")
72+
setting 'xpack.security.operator_privileges.enabled', "true"
73+
user username: "non_operator", password: 'x-pack-test-password', role: "superuser"
74+
}
7075

7176
user username: "test_user", password: "x-pack-test-password"
7277

@@ -154,7 +159,8 @@ for (Version bwcVersion : BuildParams.bwcVersions.wireCompatible) {
154159
'mixed_cluster/90_ml_data_frame_analytics_crud/Put and delete jobs',
155160
'mixed_cluster/110_enrich/Enrich stats query smoke test for mixed cluster',
156161
'mixed_cluster/120_api_key/Test API key authentication will work in a mixed cluster',
157-
'mixed_cluster/121_api_key/Create API key with metadata in a mixed cluster'
162+
'mixed_cluster/121_api_key/Create API key with metadata in a mixed cluster',
163+
'mixed_cluster/130_operator_privileges/Test operator privileges will work in the mixed cluster'
158164
]
159165
// transform in mixed cluster is effectively disabled till 7.4, see gh#48019
160166
if (Version.fromString(oldVersion).before('7.4.0')) {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
operator:
2+
- usernames: ["test_user"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
"Test operator privileges will work in the mixed cluster":
3+
4+
- skip:
5+
features: headers
6+
version: " - 7.10.99"
7+
reason: "operator privileges are available since 7.11"
8+
9+
# The default user ("test_user") is an operator, so this works
10+
- do:
11+
cluster.delete_voting_config_exclusions: { }
12+
13+
- do:
14+
catch: forbidden
15+
headers: # the non_operator user
16+
Authorization: Basic bm9uX29wZXJhdG9yOngtcGFjay10ZXN0LXBhc3N3b3Jk
17+
cluster.delete_voting_config_exclusions: { }

0 commit comments

Comments
 (0)