diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java index 1bdbb6e6c4c5..3e47b63f3c8e 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/SecurityContext.java @@ -21,6 +21,7 @@ import org.elasticsearch.node.Node; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.Authentication.AuthenticationType; +import org.elasticsearch.xpack.core.security.authc.AuthenticationField; import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.authc.support.SecondaryAuthentication; import org.elasticsearch.xpack.core.security.user.User; @@ -157,12 +158,19 @@ public T executeWithAuthentication(Authentication authentication, Function consumer, Version version) { + // Preserve request headers other than authentication + final Map existingRequestHeaders = threadContext.getRequestHeadersOnly(); final StoredContext original = threadContext.newStoredContext(true); final Authentication authentication = getAuthentication(); try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { setAuthentication(new Authentication(authentication.getUser(), authentication.getAuthenticatedBy(), authentication.getLookedUpBy(), version, authentication.getAuthenticationType(), rewriteMetadataForApiKeyRoleDescriptors(version, authentication))); + existingRequestHeaders.forEach((k, v) -> { + if (false == AuthenticationField.AUTHENTICATION_KEY.equals(k)) { + threadContext.putHeader(k, v); + } + }); consumer.accept(original); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityContextTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityContextTests.java index 4be9bb184f0e..00aba9f426a2 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityContextTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityContextTests.java @@ -21,6 +21,7 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationField; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; +import org.elasticsearch.xpack.security.audit.AuditUtil; import org.junit.Before; import java.io.EOFException; @@ -33,6 +34,7 @@ import static org.elasticsearch.xpack.core.security.authc.Authentication.VERSION_API_KEY_ROLES_AS_BYTES; import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY; import static org.elasticsearch.xpack.core.security.authc.AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.instanceOf; public class SecurityContextTests extends ESTestCase { @@ -119,6 +121,11 @@ public void testExecuteAfterRewritingAuthentication() throws IOException { RealmRef authBy = new RealmRef("ldap", "foo", "node1"); final Authentication original = new Authentication(user, authBy, authBy); original.writeToContext(threadContext); + final Map requestHeaders = Map.of( + AuthenticationField.PRIVILEGE_CATEGORY_KEY, randomAlphaOfLengthBetween(3, 10), + randomAlphaOfLengthBetween(3, 8), randomAlphaOfLengthBetween(3, 8) + ); + threadContext.putHeader(requestHeaders); final AtomicReference contextAtomicReference = new AtomicReference<>(); securityContext.executeAfterRewritingAuthentication(originalCtx -> { @@ -129,6 +136,8 @@ public void testExecuteAfterRewritingAuthentication() throws IOException { assertEquals(VersionUtils.getPreviousVersion(), authentication.getVersion()); assertEquals(original.getAuthenticationType(), securityContext.getAuthentication().getAuthenticationType()); contextAtomicReference.set(originalCtx); + // Other request headers should be preserved + requestHeaders.forEach((k, v) -> assertThat(threadContext.getHeader(k), equalTo(v))); }, VersionUtils.getPreviousVersion()); final Authentication authAfterExecution = securityContext.getAuthentication(); diff --git a/x-pack/qa/rolling-upgrade/build.gradle b/x-pack/qa/rolling-upgrade/build.gradle index 306b87a2d76d..4589ece357e7 100644 --- a/x-pack/qa/rolling-upgrade/build.gradle +++ b/x-pack/qa/rolling-upgrade/build.gradle @@ -68,7 +68,7 @@ BuildParams.bwcVersions.withWireCompatiple { bwcVersion, baseName -> } if (bwcVersion.onOrAfter('7.11.0')) { extraConfigFile 'operator_users.yml', file("${project.projectDir}/src/test/resources/operator_users.yml") -// setting 'xpack.security.operator_privileges.enabled', "true" + setting 'xpack.security.operator_privileges.enabled', "true" user username: "non_operator", password: 'x-pack-test-password', role: "superuser" } @@ -136,7 +136,8 @@ BuildParams.bwcVersions.withWireCompatiple { bwcVersion, baseName -> 'mixed_cluster/90_ml_data_frame_analytics_crud/Put an outlier_detection job on the mixed cluster', 'mixed_cluster/110_enrich/Enrich stats query smoke test for mixed cluster', 'mixed_cluster/120_api_key/Test API key authentication will work in a mixed cluster', - 'mixed_cluster/120_api_key/Create API key with metadata in a mixed cluster' + 'mixed_cluster/120_api_key/Create API key with metadata in a mixed cluster', + 'mixed_cluster/130_operator_privileges/Test operator pivileges will work in the mixed cluster' ].join(',') } diff --git a/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/mixed_cluster/130_operator_privileges.yml b/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/mixed_cluster/130_operator_privileges.yml index dbba6c09124b..d1f0602c2e5f 100644 --- a/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/mixed_cluster/130_operator_privileges.yml +++ b/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/mixed_cluster/130_operator_privileges.yml @@ -9,8 +9,8 @@ - do: cluster.delete_voting_config_exclusions: { } -# - do: -# catch: forbidden -# headers: # the non_operator user -# Authorization: Basic bm9uX29wZXJhdG9yOngtcGFjay10ZXN0LXBhc3N3b3Jk -# cluster.delete_voting_config_exclusions: { } + - do: + catch: forbidden + headers: # the non_operator user + Authorization: Basic bm9uX29wZXJhdG9yOngtcGFjay10ZXN0LXBhc3N3b3Jk + cluster.delete_voting_config_exclusions: { } diff --git a/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/130_operator_privileges.yml b/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/130_operator_privileges.yml deleted file mode 100644 index c39c61dd69be..000000000000 --- a/x-pack/qa/rolling-upgrade/src/test/resources/rest-api-spec/test/old_cluster/130_operator_privileges.yml +++ /dev/null @@ -1,17 +0,0 @@ ---- -"Test operator pivileges will work in the old cluster": - - - skip: - features: headers - version: " - 7.10.99" - reason: "operator privileges are available since 7.11" - - - do: - cluster.delete_voting_config_exclusions: { } -# -# - do: -# catch: forbidden -# headers: # the non_operator user -# Authorization: Basic bm9uX29wZXJhdG9yOngtcGFjay10ZXN0LXBhc3N3b3Jk -# cluster.delete_voting_config_exclusions: {} -