From 222073140f4a0ce21e4eebfa261bc395efbf0850 Mon Sep 17 00:00:00 2001 From: Graeme Mjehovich <59065536+gmjehovich@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:23:38 -0400 Subject: [PATCH 01/13] Show Original User in Slow Logs for RCS 2.0 --- .../xpack/security/Security.java | 78 +++- .../xpack/security/SecurityTests.java | 375 ++++++++++++++++++ 2 files changed, 433 insertions(+), 20 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 740c11ea97306..dfaab052685f8 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -2403,34 +2403,72 @@ private void reloadRemoteClusterCredentials(Settings settingsWithKeystore) { public Map getAuthContextForSlowLog() { if (this.securityContext.get() != null && this.securityContext.get().getAuthentication() != null) { Authentication authentication = this.securityContext.get().getAuthentication(); - Subject authenticatingSubject = authentication.getAuthenticatingSubject(); - Subject effetctiveSubject = authentication.getEffectiveSubject(); Map authContext = new HashMap<>(); - if (authenticatingSubject.getUser() != null) { - authContext.put("user.name", authenticatingSubject.getUser().principal()); - authContext.put("user.realm", authenticatingSubject.getRealm().getName()); - if (authenticatingSubject.getUser().fullName() != null) { - authContext.put("user.full_name", authenticatingSubject.getUser().fullName()); - } - } - // Only include effective user if different from authenticating user (run-as) - if (effetctiveSubject.getUser() != null && effetctiveSubject.equals(authenticatingSubject) == false) { - authContext.put("user.effective.name", effetctiveSubject.getUser().principal()); - authContext.put("user.effective.realm", effetctiveSubject.getRealm().getName()); - if (effetctiveSubject.getUser().fullName() != null) { - authContext.put("user.effective.full_name", effetctiveSubject.getUser().fullName()); - } + + // Handle Cross-Cluster Access (RCS 2.0) scenario + if (authentication.isCrossClusterAccess()) { + Authentication originalAuthentication = Authentication.getAuthenticationFromCrossClusterAccessMetadata(authentication); + // Call the helper method for the originalAuthentication (from querying cluster) + populateAuthContextMap(originalAuthentication, authContext); } - authContext.put("auth.type", authentication.getAuthenticationType().name()); - if (authentication.isApiKey()) { - authContext.put("apikey.id", authenticatingSubject.getMetadata().get(AuthenticationField.API_KEY_ID_KEY).toString()); - authContext.put("apikey.name", authenticatingSubject.getMetadata().get(AuthenticationField.API_KEY_NAME_KEY).toString()); + // Logic for obtaining non-cross-cluster access authentication information + else { + // Call the helper method for the authentication itself + populateAuthContextMap(authentication, authContext); } return authContext; } return Map.of(); } + /** + * Helper method to populate authentication context fields for slow logs. + * This logic is common for both direct authentications and the nested + * original authentication in cross-cluster access scenarios. + * + * @param auth The Authentication object to extract details from. + * @param authContext The map to populate with authentication details. + */ + private void populateAuthContextMap(Authentication auth, Map authContext) { + Subject authenticatingSubject = auth.getAuthenticatingSubject(); + Subject effectiveSubject = auth.getEffectiveSubject(); + + // The primary user.name and user.realm fields should reflect the AUTHENTICATING user + if (authenticatingSubject.getUser() != null) { + authContext.put("user.name", authenticatingSubject.getUser().principal()); + authContext.put("user.realm", authenticatingSubject.getRealm().getName()); + if (authenticatingSubject.getUser().fullName() != null) { + authContext.put("user.full_name", authenticatingSubject.getUser().fullName()); + } + } + + // Only include effective user if different from authenticating user (run-as) + if (auth.isRunAs()) { // Use auth.isRunAs() for consistency + if (effectiveSubject.getUser() != null) { + authContext.put("user.effective.name", effectiveSubject.getUser().principal()); + authContext.put("user.effective.realm", effectiveSubject.getRealm().getName()); + if (effectiveSubject.getUser().fullName() != null) { + authContext.put("user.effective.full_name", effectiveSubject.getUser().fullName()); + } + } + } + + // Auth type + authContext.put("auth.type", auth.getAuthenticationType().name()); + + // Add API key details if this authentication was an API key itself + if (auth.isApiKey()) { + // These metadata fields are expected to be on the authenticating subject of the API key + // Use Objects.toString() for safety against null metadata values if not strictly guaranteed + authContext.put("apikey.id", Objects.toString(authenticatingSubject.getMetadata().get(AuthenticationField.API_KEY_ID_KEY))); + + Object apiKeyName = authenticatingSubject.getMetadata().get(AuthenticationField.API_KEY_NAME_KEY); + if (apiKeyName != null) { // Name can be null for API keys, so check explicitly + authContext.put("apikey.name", apiKeyName.toString()); + } + } + } + static final class ValidateLicenseForFIPS implements BiConsumer { private final boolean inFipsMode; private final LicenseService licenseService; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index e480fb69b61e2..19a6ec077beee 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -84,19 +84,25 @@ import org.elasticsearch.xpack.core.security.action.ActionTypes; import org.elasticsearch.xpack.core.security.action.service.TokenInfo; import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.authc.AuthenticationField; +import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper; +import org.elasticsearch.xpack.core.security.authc.CrossClusterAccessSubjectInfo; import org.elasticsearch.xpack.core.security.authc.Realm; import org.elasticsearch.xpack.core.security.authc.RealmConfig; import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.security.authc.file.FileRealmSettings; import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountToken; import org.elasticsearch.xpack.core.security.authc.service.ServiceAccountTokenStore; +import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.authc.support.CachingUsernamePasswordRealmSettings; import org.elasticsearch.xpack.core.security.authc.support.Hasher; +import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection; import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.xpack.core.security.authz.permission.DocumentPermissions; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition; +import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.core.ssl.SSLService; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.audit.logfile.LoggingAuditTrail; @@ -1233,6 +1239,375 @@ public List loadExtensions(Class extensionPointType) { assertThat(operatorPrivilegesService, is(NOOP_OPERATOR_PRIVILEGES_SERVICE)); } + public void testAuthContextForSlowLog_LocalAccess_OriginalRealmUser() throws Exception { + createComponents(Settings.EMPTY); + AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); + + User searchUser = new User( + "username", + new String[] { "user_role" }, + "User Full Name", + "user@example.com", + Collections.emptyMap(), + true + ); + Authentication.RealmRef userRealm = new Authentication.RealmRef( + "default_native", + "native", + "nodeName" + ); + + Authentication realmAuth = Authentication.newRealmAuthentication(searchUser, userRealm); + serializer.writeToContext(realmAuth, threadContext); + + Map authContextRealm = security.getAuthContextForSlowLog(); + + assertThat(authContextRealm.get("user.name"), equalTo("username")); + assertThat(authContextRealm.get("user.realm"), equalTo("default_native")); + assertThat(authContextRealm.get("user.full_name"), equalTo("User Full Name")); + assertThat(authContextRealm.get("auth.type"), equalTo(Authentication.AuthenticationType.REALM.name())); + assertFalse(authContextRealm.containsKey("user.effective.name")); + assertFalse(authContextRealm.containsKey("apikey.id")); + assertFalse(authContextRealm.containsKey("apikey.name")); + } + + /** + * Tests getAuthContextForSlowLog for an API Key authentication scenario. + * Covers a part of the 'else' branch where authentication is NOT cross-cluster. + */ + public void testAuthContextForSlowLog_LocalAccess_ApiKeyAuthentication() throws Exception { + createComponents(Settings.EMPTY); + AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); + + Map apiKeyMetadata = new HashMap<>(); + apiKeyMetadata.put(AuthenticationField.API_KEY_ID_KEY, "test_local_api_key_id_123"); + apiKeyMetadata.put(AuthenticationField.API_KEY_NAME_KEY, "MyLocalTestApiKey"); + + User apiKeyUser = new User( + "local_api_key_principal", + new String[0], + null, + null, + Collections.emptyMap(), + true + ); + + AuthenticationResult apiKeyAuthResult = AuthenticationResult.success(apiKeyUser, apiKeyMetadata); + + Authentication apiKeyAuth = Authentication.newApiKeyAuthentication(apiKeyAuthResult, "local_node_api_key_origin"); + + serializer.writeToContext(apiKeyAuth, threadContext); + + Map authContext = security.getAuthContextForSlowLog(); + + assertNotNull(authContext); + assertThat(authContext.get("user.name"), equalTo("local_api_key_principal")); + assertThat(authContext.get("user.realm"), equalTo(AuthenticationField.API_KEY_REALM_NAME)); + assertFalse(authContext.containsKey("user.full_name")); + assertThat(authContext.get("auth.type"), equalTo(Authentication.AuthenticationType.API_KEY.name())); + assertThat(authContext.get("apikey.id"), equalTo("test_local_api_key_id_123")); + assertThat(authContext.get("apikey.name"), equalTo("MyLocalTestApiKey")); + assertFalse(authContext.containsKey("user.effective.name")); // Not a run-as scenario + assertFalse(authContext.containsKey("user.effective.realm")); + } + + /** + * Tests getAuthContextForSlowLog for a Run-as authentication scenario. + * Covers a part of the 'else' branch where authentication is NOT cross-cluster. + */ + public void testAuthContextForSlowLog_LocalAccess_RunAsAuthentication() throws Exception { + createComponents(Settings.EMPTY); + AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); + + // Define the authenticating user + User authenticatingUser = new User( + "authenticating_user", + new String[]{"admin"}, + "Authenticating User", + "test@example.com", + Collections.emptyMap(), + true + ); + + Authentication.RealmRef authenticatingRealm = new Authentication.RealmRef( + "local_file_realm", + "file", + "local_node_authenticators" + ); + + Authentication baseAuth = Authentication.newRealmAuthentication(authenticatingUser, authenticatingRealm); + + // Define the effective user (the one being run-as) + User effectiveUser = new User( + "run_as_user", + new String[]{"run_as"}, + "Run As User", + "test2@example.com", + Collections.emptyMap(), + true + ); + + Authentication.RealmRef effectiveRealm = new Authentication.RealmRef( + "local_ldap_realm", + "ldap", + "local_node_ldap" + ); + + Authentication runAsAuth = baseAuth.runAs(effectiveUser, effectiveRealm); + assertTrue(runAsAuth.isRunAs()); + + serializer.writeToContext(runAsAuth, threadContext); + + Map authContext = security.getAuthContextForSlowLog(); + + assertNotNull(authContext); + assertThat(authContext.get("user.name"), equalTo("authenticating_user")); + assertThat(authContext.get("user.realm"), equalTo("local_file_realm")); + assertThat(authContext.get("user.full_name"), equalTo("Authenticating User")); + + assertThat(authContext.get("user.effective.name"), equalTo("run_as_user")); + assertThat(authContext.get("user.effective.realm"), equalTo("local_ldap_realm")); + assertThat(authContext.get("user.effective.full_name"), equalTo("Run As User")); + + assertThat(authContext.get("auth.type"), equalTo(Authentication.AuthenticationType.REALM.name())); + assertFalse(authContext.containsKey("apikey.id")); + assertFalse(authContext.containsKey("apikey.name")); + } + + /** + * Tests getAuthContextForSlowLog for a Cross-Cluster Access scenario + * where the original user on the querying cluster authenticated via a Realm. + */ + public void testAuthContextForSlowLog_CCA_OriginalRealmUser() throws Exception { + createComponents(Settings.EMPTY); + + // Create inner Authentication object for the 'remote_search_user' + User remoteSearchUser = new User( + "remote_search_user", + new String[] { "remote_role" }, + "Remote Search User Full Name", + "remote@example.com", + Collections.emptyMap(), + true + ); + Authentication.RealmRef remoteSearchRealm = new Authentication.RealmRef( + "default_native", + "native", + "node_name_querying_cluster" + ); + Authentication originalAuthentication = Authentication.newRealmAuthentication(remoteSearchUser, remoteSearchRealm); + + CrossClusterAccessSubjectInfo crossClusterAccessSubjectInfo = new CrossClusterAccessSubjectInfo( + originalAuthentication, + RoleDescriptorsIntersection.EMPTY + ); + + // Create outer Authentication object (the cross-cluster API key type) + User dummyApiKeyUser = new User( + "dummy_api_key_principal", + new String[0], + null, + null, + Collections.emptyMap(), + true + ); + + Map authResultMetadata = new HashMap<>(); + authResultMetadata.put(AuthenticationField.API_KEY_ID_KEY, "test_api_key_unique_id_from_auth_result"); + authResultMetadata.put(AuthenticationField.API_KEY_NAME_KEY, "Test CCS API Key Name from AuthResult"); + + AuthenticationResult apiAuthResult = AuthenticationResult.success(dummyApiKeyUser, authResultMetadata); + + Authentication baseApiKeyAuth = Authentication.newApiKeyAuthentication(apiAuthResult, "node_name_fulfilling_cluster"); + + Authentication outerCrossClusterAccessAuth = baseApiKeyAuth.toCrossClusterAccess(crossClusterAccessSubjectInfo); + + assertTrue(outerCrossClusterAccessAuth.isCrossClusterAccess()); + assertThat(outerCrossClusterAccessAuth.getAuthenticatingSubject().getUser().principal(), equalTo("dummy_api_key_principal")); + + assertNotNull( + outerCrossClusterAccessAuth.getAuthenticatingSubject() + .getMetadata() + .get(AuthenticationField.CROSS_CLUSTER_ACCESS_AUTHENTICATION_KEY) + ); + + AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); + serializer.writeToContext(outerCrossClusterAccessAuth, threadContext); + + Map authContext = security.getAuthContextForSlowLog(); + + assertNotNull(authContext); + assertThat(authContext.get("user.name"), equalTo("remote_search_user")); + assertThat(authContext.get("user.realm"), equalTo("default_native")); + assertThat(authContext.get("user.full_name"), equalTo("Remote Search User Full Name")); + assertThat(authContext.get("auth.type"), equalTo(Authentication.AuthenticationType.REALM.name())); + } + + /** + * Tests getAuthContextForSlowLog for a Cross-Cluster Access scenario + * where the original user on the querying cluster authenticated via an API Key. + */ + public void testAuthContextForSlowLog_CCA_OriginalApiKeyUser() throws Exception { + createComponents(Settings.EMPTY); + AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); + + // Original user authenticated via an API Key on the querying cluster + User originalApiKeyUser = new User( + "original_api_key_principal", + new String[0], + null, + null, + Collections.emptyMap(), + true + ); + + Map originalApiKeyMetadata = new HashMap<>(); + originalApiKeyMetadata.put(AuthenticationField.API_KEY_ID_KEY, "original_api_key_id_xyz"); + originalApiKeyMetadata.put(AuthenticationField.API_KEY_NAME_KEY, "Original Remote API Key Name"); + + Authentication originalAuthenticationApiKey = Authentication.newApiKeyAuthentication( + AuthenticationResult.success(originalApiKeyUser, originalApiKeyMetadata), + "node_querying_apikey" + ); + + // Wrap the original Authentication + CrossClusterAccessSubjectInfo ccasiApiKey = new CrossClusterAccessSubjectInfo( + originalAuthenticationApiKey, + RoleDescriptorsIntersection.EMPTY + ); + + User dummyApiKeyUserForApiKey = new User( + "dummy_api_key_id_apikey", + new String[0], + null, + null, + Map.of(AuthenticationField.API_KEY_ID_KEY, "api_id_apikey"), + true + ); + + Authentication baseApiKeyAuthApiKey = Authentication.newApiKeyAuthentication( + AuthenticationResult.success( + dummyApiKeyUserForApiKey, + Map.of(AuthenticationField.API_KEY_ID_KEY, "api_id_apikey") + ), + "node_fulfilling_apikey" + ); + + Authentication outerCrossClusterAccessAuthApiKey = baseApiKeyAuthApiKey.toCrossClusterAccess(ccasiApiKey); + + serializer.writeToContext(outerCrossClusterAccessAuthApiKey, threadContext); + + Map authContext = security.getAuthContextForSlowLog(); + + assertNotNull(authContext); + assertThat(authContext.get("user.name"), equalTo("original_api_key_principal")); + // API key realm name from the original authentication (AuthenticationField.API_KEY_REALM_NAME) + assertThat(authContext.get("user.realm"), equalTo(AuthenticationField.API_KEY_REALM_NAME)); + assertThat(authContext.get("auth.type"), equalTo(Authentication.AuthenticationType.API_KEY.name())); + assertThat(authContext.get("apikey.id"), equalTo("original_api_key_id_xyz")); + assertThat(authContext.get("apikey.name"), equalTo("Original Remote API Key Name")); + assertFalse(authContext.containsKey("user.effective.name")); + } + + /** + * Tests getAuthContextForSlowLog for a Cross-Cluster Access scenario + * where the original user on the querying cluster was a Run-As user. + */ + public void testAuthContextForSlowLog_CCA_OriginalRunAsUser() throws Exception { + createComponents(Settings.EMPTY); + AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); + + // Authenticating user on querying cluster (who performs the run-as) + User authenticatingRemoteUser = new User( + "authenticating_remote", + new String[]{"power_user"}, + "Authenticating Remote User", + null, + Collections.emptyMap(), + true + ); + + Authentication.RealmRef authenticatingRemoteRealm = new Authentication.RealmRef( + "remote_auth_realm", + "ldap", + "node_querying_auth" + ); + + Authentication baseAuthenticationRemote = Authentication.newRealmAuthentication(authenticatingRemoteUser, authenticatingRemoteRealm); + + // Effective user (the one being run-as) on querying cluster + User effectiveRemoteUser = new User( + "effective_remote", + new String[]{"readonly"}, + "Effective Remote User", + null, + Collections.emptyMap(), + true + ); + + Authentication.RealmRef effectiveRemoteRealm = new Authentication.RealmRef( + "remote_effective_realm", + "file", + "node_querying_effective" + ); + + // Create the run-as authentication for the original user + Authentication originalAuthenticationRunAs = baseAuthenticationRemote.runAs(effectiveRemoteUser, effectiveRemoteRealm); + assertTrue(originalAuthenticationRunAs.isRunAs()); // Verify it's a run-as type + + // Build the CrossClusterAccessSubjectInfo wrapping the original Authentication + CrossClusterAccessSubjectInfo ccasiRunAs = new CrossClusterAccessSubjectInfo( + originalAuthenticationRunAs, + RoleDescriptorsIntersection.EMPTY + ); + + User dummyApiKeyUserForRunAs = new User( + "dummy_api_key_id_runas", + new String[0], + null, + null, + Map.of( + AuthenticationField.API_KEY_ID_KEY, + "api_id_runas" + ), + true); + Authentication baseApiKeyAuthRunAs = Authentication.newApiKeyAuthentication( + AuthenticationResult.success( + dummyApiKeyUserForRunAs, + Map.of( + AuthenticationField.API_KEY_ID_KEY, + "api_id_runas" + ) + ), + "node_fulfilling_runas" + ); + + // Convert to the outer Cross-Cluster Access Authentication (what the fulfilling cluster sees) + Authentication outerCrossClusterAccessAuthRunAs = baseApiKeyAuthRunAs.toCrossClusterAccess(ccasiRunAs); + + serializer.writeToContext(outerCrossClusterAccessAuthRunAs, threadContext); + + // Call the method under test + Map authContext = security.getAuthContextForSlowLog(); + + // Assert the results + assertNotNull(authContext); + // user.name/realm should reflect the AUTHENTICATING user from querying cluster + assertThat(authContext.get("user.name"), equalTo("authenticating_remote")); + assertThat(authContext.get("user.realm"), equalTo("remote_auth_realm")); + assertThat(authContext.get("user.full_name"), equalTo("Authenticating Remote User")); + + // user.effective.* should reflect the EFFECTIVE user from querying cluster + assertThat(authContext.get("user.effective.name"), equalTo("effective_remote")); + assertThat(authContext.get("user.effective.realm"), equalTo("remote_effective_realm")); + assertThat(authContext.get("user.effective.full_name"), equalTo("Effective Remote User")); + + assertThat(authContext.get("auth.type"), equalTo(Authentication.AuthenticationType.REALM.name())); // Type based on base auth + assertFalse(authContext.containsKey("apikey.id")); // Not an API key + assertFalse(authContext.containsKey("apikey.name")); + } + + private void verifyHasAuthenticationHeaderValue(Exception e, String... expectedValues) { assertThat(e, instanceOf(ElasticsearchSecurityException.class)); assertThat(((ElasticsearchSecurityException) e).getHeader("WWW-Authenticate"), notNullValue()); From 0e6029d59cfdcfa7b8ec9d3cf18a6b2046058800 Mon Sep 17 00:00:00 2001 From: Graeme Mjehovich <59065536+gmjehovich@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:35:38 -0400 Subject: [PATCH 02/13] fix commenting --- .../elasticsearch/xpack/security/SecurityTests.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 19a6ec077beee..298310f6a0e80 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -1239,6 +1239,7 @@ public List loadExtensions(Class extensionPointType) { assertThat(operatorPrivilegesService, is(NOOP_OPERATOR_PRIVILEGES_SERVICE)); } + public void testAuthContextForSlowLog_LocalAccess_OriginalRealmUser() throws Exception { createComponents(Settings.EMPTY); AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); @@ -1570,7 +1571,9 @@ public void testAuthContextForSlowLog_CCA_OriginalRunAsUser() throws Exception { AuthenticationField.API_KEY_ID_KEY, "api_id_runas" ), - true); + true + ); + Authentication baseApiKeyAuthRunAs = Authentication.newApiKeyAuthentication( AuthenticationResult.success( dummyApiKeyUserForRunAs, @@ -1587,23 +1590,19 @@ public void testAuthContextForSlowLog_CCA_OriginalRunAsUser() throws Exception { serializer.writeToContext(outerCrossClusterAccessAuthRunAs, threadContext); - // Call the method under test Map authContext = security.getAuthContextForSlowLog(); - // Assert the results assertNotNull(authContext); - // user.name/realm should reflect the AUTHENTICATING user from querying cluster assertThat(authContext.get("user.name"), equalTo("authenticating_remote")); assertThat(authContext.get("user.realm"), equalTo("remote_auth_realm")); assertThat(authContext.get("user.full_name"), equalTo("Authenticating Remote User")); - // user.effective.* should reflect the EFFECTIVE user from querying cluster assertThat(authContext.get("user.effective.name"), equalTo("effective_remote")); assertThat(authContext.get("user.effective.realm"), equalTo("remote_effective_realm")); assertThat(authContext.get("user.effective.full_name"), equalTo("Effective Remote User")); - assertThat(authContext.get("auth.type"), equalTo(Authentication.AuthenticationType.REALM.name())); // Type based on base auth - assertFalse(authContext.containsKey("apikey.id")); // Not an API key + assertThat(authContext.get("auth.type"), equalTo(Authentication.AuthenticationType.REALM.name())); + assertFalse(authContext.containsKey("apikey.id")); assertFalse(authContext.containsKey("apikey.name")); } From feead9d250b3806ce79ce103fc7faf3598d97938 Mon Sep 17 00:00:00 2001 From: Graeme Mjehovich <59065536+gmjehovich@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:56:58 -0400 Subject: [PATCH 03/13] Update docs/changelog/130140.yaml --- docs/changelog/130140.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 docs/changelog/130140.yaml diff --git a/docs/changelog/130140.yaml b/docs/changelog/130140.yaml new file mode 100644 index 0000000000000..dc49dd81e2445 --- /dev/null +++ b/docs/changelog/130140.yaml @@ -0,0 +1,5 @@ +pr: 130140 +summary: Correct slow log user for RCS 2.0 +area: Authentication +type: enhancement +issues: [] From eb2006ea62ca03883f813b444adc7dc630c28111 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Thu, 26 Jun 2025 21:05:48 +0000 Subject: [PATCH 04/13] [CI] Auto commit changes from spotless --- .../xpack/security/SecurityTests.java | 90 ++++--------------- 1 file changed, 19 insertions(+), 71 deletions(-) diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 298310f6a0e80..51671cff5d004 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -1239,7 +1239,6 @@ public List loadExtensions(Class extensionPointType) { assertThat(operatorPrivilegesService, is(NOOP_OPERATOR_PRIVILEGES_SERVICE)); } - public void testAuthContextForSlowLog_LocalAccess_OriginalRealmUser() throws Exception { createComponents(Settings.EMPTY); AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); @@ -1252,11 +1251,7 @@ public void testAuthContextForSlowLog_LocalAccess_OriginalRealmUser() throws Exc Collections.emptyMap(), true ); - Authentication.RealmRef userRealm = new Authentication.RealmRef( - "default_native", - "native", - "nodeName" - ); + Authentication.RealmRef userRealm = new Authentication.RealmRef("default_native", "native", "nodeName"); Authentication realmAuth = Authentication.newRealmAuthentication(searchUser, userRealm); serializer.writeToContext(realmAuth, threadContext); @@ -1284,14 +1279,7 @@ public void testAuthContextForSlowLog_LocalAccess_ApiKeyAuthentication() throws apiKeyMetadata.put(AuthenticationField.API_KEY_ID_KEY, "test_local_api_key_id_123"); apiKeyMetadata.put(AuthenticationField.API_KEY_NAME_KEY, "MyLocalTestApiKey"); - User apiKeyUser = new User( - "local_api_key_principal", - new String[0], - null, - null, - Collections.emptyMap(), - true - ); + User apiKeyUser = new User("local_api_key_principal", new String[0], null, null, Collections.emptyMap(), true); AuthenticationResult apiKeyAuthResult = AuthenticationResult.success(apiKeyUser, apiKeyMetadata); @@ -1323,36 +1311,28 @@ public void testAuthContextForSlowLog_LocalAccess_RunAsAuthentication() throws E // Define the authenticating user User authenticatingUser = new User( "authenticating_user", - new String[]{"admin"}, + new String[] { "admin" }, "Authenticating User", "test@example.com", Collections.emptyMap(), true ); - Authentication.RealmRef authenticatingRealm = new Authentication.RealmRef( - "local_file_realm", - "file", - "local_node_authenticators" - ); + Authentication.RealmRef authenticatingRealm = new Authentication.RealmRef("local_file_realm", "file", "local_node_authenticators"); Authentication baseAuth = Authentication.newRealmAuthentication(authenticatingUser, authenticatingRealm); // Define the effective user (the one being run-as) User effectiveUser = new User( "run_as_user", - new String[]{"run_as"}, + new String[] { "run_as" }, "Run As User", "test2@example.com", Collections.emptyMap(), true ); - Authentication.RealmRef effectiveRealm = new Authentication.RealmRef( - "local_ldap_realm", - "ldap", - "local_node_ldap" - ); + Authentication.RealmRef effectiveRealm = new Authentication.RealmRef("local_ldap_realm", "ldap", "local_node_ldap"); Authentication runAsAuth = baseAuth.runAs(effectiveUser, effectiveRealm); assertTrue(runAsAuth.isRunAs()); @@ -1391,11 +1371,7 @@ public void testAuthContextForSlowLog_CCA_OriginalRealmUser() throws Exception { Collections.emptyMap(), true ); - Authentication.RealmRef remoteSearchRealm = new Authentication.RealmRef( - "default_native", - "native", - "node_name_querying_cluster" - ); + Authentication.RealmRef remoteSearchRealm = new Authentication.RealmRef("default_native", "native", "node_name_querying_cluster"); Authentication originalAuthentication = Authentication.newRealmAuthentication(remoteSearchUser, remoteSearchRealm); CrossClusterAccessSubjectInfo crossClusterAccessSubjectInfo = new CrossClusterAccessSubjectInfo( @@ -1404,14 +1380,7 @@ public void testAuthContextForSlowLog_CCA_OriginalRealmUser() throws Exception { ); // Create outer Authentication object (the cross-cluster API key type) - User dummyApiKeyUser = new User( - "dummy_api_key_principal", - new String[0], - null, - null, - Collections.emptyMap(), - true - ); + User dummyApiKeyUser = new User("dummy_api_key_principal", new String[0], null, null, Collections.emptyMap(), true); Map authResultMetadata = new HashMap<>(); authResultMetadata.put(AuthenticationField.API_KEY_ID_KEY, "test_api_key_unique_id_from_auth_result"); @@ -1453,14 +1422,7 @@ public void testAuthContextForSlowLog_CCA_OriginalApiKeyUser() throws Exception AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); // Original user authenticated via an API Key on the querying cluster - User originalApiKeyUser = new User( - "original_api_key_principal", - new String[0], - null, - null, - Collections.emptyMap(), - true - ); + User originalApiKeyUser = new User("original_api_key_principal", new String[0], null, null, Collections.emptyMap(), true); Map originalApiKeyMetadata = new HashMap<>(); originalApiKeyMetadata.put(AuthenticationField.API_KEY_ID_KEY, "original_api_key_id_xyz"); @@ -1487,10 +1449,7 @@ public void testAuthContextForSlowLog_CCA_OriginalApiKeyUser() throws Exception ); Authentication baseApiKeyAuthApiKey = Authentication.newApiKeyAuthentication( - AuthenticationResult.success( - dummyApiKeyUserForApiKey, - Map.of(AuthenticationField.API_KEY_ID_KEY, "api_id_apikey") - ), + AuthenticationResult.success(dummyApiKeyUserForApiKey, Map.of(AuthenticationField.API_KEY_ID_KEY, "api_id_apikey")), "node_fulfilling_apikey" ); @@ -1521,25 +1480,24 @@ public void testAuthContextForSlowLog_CCA_OriginalRunAsUser() throws Exception { // Authenticating user on querying cluster (who performs the run-as) User authenticatingRemoteUser = new User( "authenticating_remote", - new String[]{"power_user"}, + new String[] { "power_user" }, "Authenticating Remote User", null, Collections.emptyMap(), true ); - Authentication.RealmRef authenticatingRemoteRealm = new Authentication.RealmRef( - "remote_auth_realm", - "ldap", - "node_querying_auth" - ); + Authentication.RealmRef authenticatingRemoteRealm = new Authentication.RealmRef("remote_auth_realm", "ldap", "node_querying_auth"); - Authentication baseAuthenticationRemote = Authentication.newRealmAuthentication(authenticatingRemoteUser, authenticatingRemoteRealm); + Authentication baseAuthenticationRemote = Authentication.newRealmAuthentication( + authenticatingRemoteUser, + authenticatingRemoteRealm + ); // Effective user (the one being run-as) on querying cluster User effectiveRemoteUser = new User( "effective_remote", - new String[]{"readonly"}, + new String[] { "readonly" }, "Effective Remote User", null, Collections.emptyMap(), @@ -1567,21 +1525,12 @@ public void testAuthContextForSlowLog_CCA_OriginalRunAsUser() throws Exception { new String[0], null, null, - Map.of( - AuthenticationField.API_KEY_ID_KEY, - "api_id_runas" - ), + Map.of(AuthenticationField.API_KEY_ID_KEY, "api_id_runas"), true ); Authentication baseApiKeyAuthRunAs = Authentication.newApiKeyAuthentication( - AuthenticationResult.success( - dummyApiKeyUserForRunAs, - Map.of( - AuthenticationField.API_KEY_ID_KEY, - "api_id_runas" - ) - ), + AuthenticationResult.success(dummyApiKeyUserForRunAs, Map.of(AuthenticationField.API_KEY_ID_KEY, "api_id_runas")), "node_fulfilling_runas" ); @@ -1606,7 +1555,6 @@ public void testAuthContextForSlowLog_CCA_OriginalRunAsUser() throws Exception { assertFalse(authContext.containsKey("apikey.name")); } - private void verifyHasAuthenticationHeaderValue(Exception e, String... expectedValues) { assertThat(e, instanceOf(ElasticsearchSecurityException.class)); assertThat(((ElasticsearchSecurityException) e).getHeader("WWW-Authenticate"), notNullValue()); From d3a7cc2fd4b1f0dd913b632442123e715d069e64 Mon Sep 17 00:00:00 2001 From: Graeme Mjehovich <59065536+gmjehovich@users.noreply.github.com> Date: Thu, 10 Jul 2025 14:24:18 -0400 Subject: [PATCH 05/13] Add Integration Tests for Cross Cluster Search Slow Logs --- .../xpack/remotecluster/CcsSlowLogRestIT.java | 347 ++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java new file mode 100644 index 0000000000000..a95136dce6c74 --- /dev/null +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java @@ -0,0 +1,347 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.remotecluster; + +import org.elasticsearch.client.Request; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.Response; +import org.elasticsearch.common.io.Streams; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.core.Strings; +import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.test.cluster.LogType; +import org.elasticsearch.test.rest.ObjectPath; +import org.elasticsearch.xcontent.XContentType; +import org.junit.ClassRule; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.notNullValue; + +/** + * Integration test for verifying that slow log authentication context contains + * the correct user information for cross-cluster access scenarios. + * + * This test verifies that when cross-cluster searches are performed, the slow logs + * on the fulfilling cluster contain the authentication context of the ORIGINAL user + * from the querying cluster, not just the cross-cluster access API key. + * + * The key verification is that slow logs should show: + * - user.name: The actual user from the querying cluster (e.g., "slow_log_test_user") + * - user.realm: The realm of the original user on the querying cluster (e.g., "default_native") + * - For run-as: Both authenticating and effective users from querying cluster + */ +public class CcsSlowLogRestIT extends AbstractRemoteClusterSecurityTestCase { + + private static final AtomicReference> API_KEY_MAP_REF = new AtomicReference<>(); + + static { + fulfillingCluster = ElasticsearchCluster.local() + .name("fulfilling-cluster") + .apply(commonClusterConfig) + .setting("remote_cluster_server.enabled", "true") + .setting("remote_cluster.port", "0") + .setting("xpack.security.remote_cluster_server.ssl.enabled", "true") + .setting("xpack.security.remote_cluster_server.ssl.key", "remote-cluster.key") + .setting("xpack.security.remote_cluster_server.ssl.certificate", "remote-cluster.crt") + .keystore("xpack.security.remote_cluster_server.ssl.secure_key_passphrase", "remote-cluster-password") + .build(); + + queryCluster = ElasticsearchCluster.local() + .name("query-cluster") + .apply(commonClusterConfig) + .setting("xpack.security.remote_cluster_client.ssl.enabled", "true") + .setting("xpack.security.remote_cluster_client.ssl.certificate_authorities", "remote-cluster-ca.crt") + .keystore("cluster.remote.my_remote_cluster.credentials", () -> { + if (API_KEY_MAP_REF.get() == null) { + final Map apiKeyMap = createCrossClusterAccessApiKey(""" + { + "search": [ + { + "names": ["slow_log_*", "run_as_*"] + } + ] + }"""); + API_KEY_MAP_REF.set(apiKeyMap); + } + return (String) API_KEY_MAP_REF.get().get("encoded"); + }) + .build(); + } + + @ClassRule + public static TestRule clusterRule = RuleChain.outerRule(fulfillingCluster).around(queryCluster); + + public void testCrossClusterSlowLogAuthenticationContext() throws Exception { + configureRemoteCluster(); + + // Fulfilling cluster setup + { + // Create an index with slow log settings enabled + final Request createIndexRequest = new Request("PUT", "/slow_log_test"); + createIndexRequest.setJsonEntity(""" + { + "settings": { + "index.search.slowlog.threshold.query.trace": "0ms", + "index.search.slowlog.include.user": true + }, + "mappings": { + "properties": { + "content": { "type": "text" }, + "timestamp": { "type": "date" } + } + } + }"""); + assertOK(performRequestAgainstFulfillingCluster(createIndexRequest)); + + // test documents + final Request bulkRequest = new Request("POST", "/_bulk?refresh=true"); + bulkRequest.setJsonEntity(""" + { "index": { "_index": "slow_log_test" } } + { "content": "test content for slow log", "timestamp": "2024-01-01T10:00:00Z" } + { "index": { "_index": "slow_log_test" } } + { "content": "another test document", "timestamp": "2024-01-01T11:00:00Z" } + """); + assertOK(performRequestAgainstFulfillingCluster(bulkRequest)); + } + + // Query cluster setup + { + // Create user role with remote cluster privileges + final var putRoleRequest = new Request("PUT", "/_security/role/slow_log_remote_role"); + putRoleRequest.setJsonEntity(""" + { + "description": "Role for testing slow log auth context with cross-cluster access", + "cluster": ["manage_own_api_key"], + "remote_indices": [ + { + "names": ["slow_log_*"], + "privileges": ["read", "read_cross_cluster"], + "clusters": ["my_remote_cluster"] + } + ] + }"""); + assertOK(adminClient().performRequest(putRoleRequest)); + + // Create test user + final var putUserRequest = new Request("PUT", "/_security/user/slow_log_test_user"); + putUserRequest.setJsonEntity(""" + { + "password": "x-pack-test-password", + "roles": ["slow_log_remote_role"], + "full_name": "Slow Log Test User" + }"""); + assertOK(adminClient().performRequest(putUserRequest)); + + // Create API key for the test user + final var createApiKeyRequest = new Request("PUT", "/_security/api_key"); + createApiKeyRequest.setJsonEntity(""" + { + "name": "slow_log_test_api_key", + "role_descriptors": { + "slow_log_access": { + "remote_indices": [ + { + "names": ["slow_log_*"], + "privileges": ["read", "read_cross_cluster"], + "clusters": ["my_remote_cluster"] + } + ] + } + } + }"""); + final var createApiKeyResponse = performRequestWithSlowLogTestUser(createApiKeyRequest); + assertOK(createApiKeyResponse); + + var createApiKeyResponsePath = ObjectPath.createFromResponse(createApiKeyResponse); + final String apiKeyEncoded = createApiKeyResponsePath.evaluate("encoded"); + final String apiKeyId = createApiKeyResponsePath.evaluate("id"); + assertThat(apiKeyEncoded, notNullValue()); + assertThat(apiKeyId, notNullValue()); + + // Perform cross-cluster search that should generate slow log entries + final var searchRequest = new Request("GET", "/my_remote_cluster:slow_log_test/_search"); + searchRequest.setJsonEntity(""" + { + "query": { + "match": { + "content": "test" + } + }, + "sort": [ + { "timestamp": { "order": "desc" } } + ] + }"""); + + // Execute search with API key authentication + final Response searchResponse = performRequestWithApiKey(searchRequest, apiKeyEncoded); + assertOK(searchResponse); + + // Verify slow log contains correct authentication context from the original user + // The key test: slow logs should show the original user from querying cluster + Map expectedAuthContext = Map.of( + "user.name", "slow_log_test_user", // Original user from querying cluster + "user.realm", "default_native", // User's realm on querying cluster + "user.full_name", "Slow Log Test User", // User's full name + "auth.type", "API_KEY", // Authentication type + "apikey.id", apiKeyId, // API key from querying cluster + "apikey.name", "slow_log_test_api_key" // API key name + ); + + verifySlowLogAuthenticationContext(expectedAuthContext); + } + } + + public void testRunAsUserInCrossClusterSlowLog() throws Exception { + configureRemoteCluster(); + + // Fulfilling cluster setup + { + // Create an index for run-as testing with slow log enabled + final Request createIndexRequest = new Request("PUT", "/run_as_test"); + createIndexRequest.setJsonEntity(""" + { + "settings": { + "index.search.slowlog.threshold.query.trace": "0ms", + "index.search.slowlog.include.user": true + }, + "mappings": { + "properties": { + "data": { "type": "text" } + } + } + }"""); + assertOK(performRequestAgainstFulfillingCluster(createIndexRequest)); + + final Request indexRequest = new Request("POST", "/run_as_test/_doc?refresh=true"); + indexRequest.setJsonEntity(""" + { "data": "run as test data" }"""); + assertOK(performRequestAgainstFulfillingCluster(indexRequest)); + } + + // Query cluster setup + { + // Create role that allows run-as and remote access + final var putRunAsRoleRequest = new Request("PUT", "/_security/role/run_as_remote_role"); + putRunAsRoleRequest.setJsonEntity(""" + { + "description": "Role that can run as other users and access remote clusters", + "cluster": ["manage_own_api_key"], + "run_as": ["target_user"], + "remote_indices": [ + { + "names": ["run_as_*"], + "privileges": ["read", "read_cross_cluster"], + "clusters": ["my_remote_cluster"] + } + ] + }"""); + assertOK(adminClient().performRequest(putRunAsRoleRequest)); + + // Create the run-as user + final var putRunAsUserRequest = new Request("PUT", "/_security/user/run_as_user"); + putRunAsUserRequest.setJsonEntity(""" + { + "password": "x-pack-test-password", + "roles": ["run_as_remote_role"], + "full_name": "Run As User" + }"""); + assertOK(adminClient().performRequest(putRunAsUserRequest)); + + // Create target user (who will be run as) + final var putTargetUserRequest = new Request("PUT", "/_security/user/target_user"); + putTargetUserRequest.setJsonEntity(""" + { + "password": "x-pack-test-password", + "roles": ["run_as_remote_role"], + "full_name": "Target User" + }"""); + assertOK(adminClient().performRequest(putTargetUserRequest)); + + // Perform search with run-as header + final var runAsSearchRequest = new Request("GET", "/my_remote_cluster:run_as_test/_search"); + runAsSearchRequest.setJsonEntity(""" + { + "query": { "match_all": {} } + }"""); + + // Add both authentication and run-as headers + runAsSearchRequest.setOptions(RequestOptions.DEFAULT.toBuilder() + .addHeader("Authorization", basicAuthHeaderValue("run_as_user", PASS)) + .addHeader("es-security-runas-user", "target_user")); + + final Response runAsResponse = client().performRequest(runAsSearchRequest); + assertOK(runAsResponse); + + // Verify slow log shows both authenticating and effective users from querying cluster + Map expectedRunAsAuthContext = Map.of( + "user.name", "run_as_user", // Authenticating user from querying cluster + "user.realm", "default_native", + "user.full_name", "Run As User", + "user.effective.name", "target_user", // Effective user from querying cluster + "user.effective.realm", "default_native", + "user.effective.full_name", "Target User", + "auth.type", "REALM" + ); + + verifySlowLogAuthenticationContext(expectedRunAsAuthContext); + } + } + + /** + * Verifies that the slow logs on the fulfilling cluster contain the expected + * authentication context from the original user on the querying cluster. + */ + private void verifySlowLogAuthenticationContext(Map expectedAuthContext) throws Exception { + assertBusy(() -> { + try (var slowLog = fulfillingCluster.getNodeLog(0, LogType.SEARCH_SLOW)) { + final List lines = Streams.readAllLines(slowLog); + assert(!lines.isEmpty()); + + // Get the most recent slow log entry + String lastLogLine = lines.get(lines.size() - 1); + Map logEntry = XContentHelper.convertToMap(XContentType.JSON.xContent(), lastLogLine, true); + + // Verify that the log entry contains the expected authentication context + for (Map.Entry expectedEntry : expectedAuthContext.entrySet()) { + assertThat( + "Slow log should contain " + expectedEntry.getKey() + " with value " + expectedEntry.getValue(), + logEntry, + hasKey(expectedEntry.getKey()) + ); + assertThat( + "Slow log " + expectedEntry.getKey() + " should match expected value", + logEntry.get(expectedEntry.getKey()), + equalTo(expectedEntry.getValue()) + ); + } + } + }, 10, TimeUnit.SECONDS); + } + + private Response performRequestWithSlowLogTestUser(final Request request) throws IOException { + request.setOptions(RequestOptions.DEFAULT.toBuilder() + .addHeader("Authorization", basicAuthHeaderValue("slow_log_test_user", PASS))); + return client().performRequest(request); + } + + private Response performRequestWithApiKey(final Request request, final String encoded) throws IOException { + request.setOptions(RequestOptions.DEFAULT.toBuilder() + .addHeader("Authorization", "ApiKey " + encoded)); + return client().performRequest(request); + } +} From fd7e9e6faa20dde089bc0179b6b8b220cfb6f1fb Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Thu, 10 Jul 2025 18:38:49 +0000 Subject: [PATCH 06/13] [CI] Auto commit changes from spotless --- .../xpack/remotecluster/CcsSlowLogRestIT.java | 57 +++++++++++-------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java index a95136dce6c74..3e90d53362401 100644 --- a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java @@ -11,9 +11,7 @@ import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.Response; import org.elasticsearch.common.io.Streams; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentHelper; -import org.elasticsearch.core.Strings; import org.elasticsearch.test.cluster.ElasticsearchCluster; import org.elasticsearch.test.cluster.LogType; import org.elasticsearch.test.rest.ObjectPath; @@ -194,12 +192,18 @@ public void testCrossClusterSlowLogAuthenticationContext() throws Exception { // Verify slow log contains correct authentication context from the original user // The key test: slow logs should show the original user from querying cluster Map expectedAuthContext = Map.of( - "user.name", "slow_log_test_user", // Original user from querying cluster - "user.realm", "default_native", // User's realm on querying cluster - "user.full_name", "Slow Log Test User", // User's full name - "auth.type", "API_KEY", // Authentication type - "apikey.id", apiKeyId, // API key from querying cluster - "apikey.name", "slow_log_test_api_key" // API key name + "user.name", + "slow_log_test_user", // Original user from querying cluster + "user.realm", + "default_native", // User's realm on querying cluster + "user.full_name", + "Slow Log Test User", // User's full name + "auth.type", + "API_KEY", // Authentication type + "apikey.id", + apiKeyId, // API key from querying cluster + "apikey.name", + "slow_log_test_api_key" // API key name ); verifySlowLogAuthenticationContext(expectedAuthContext); @@ -280,22 +284,31 @@ public void testRunAsUserInCrossClusterSlowLog() throws Exception { }"""); // Add both authentication and run-as headers - runAsSearchRequest.setOptions(RequestOptions.DEFAULT.toBuilder() - .addHeader("Authorization", basicAuthHeaderValue("run_as_user", PASS)) - .addHeader("es-security-runas-user", "target_user")); + runAsSearchRequest.setOptions( + RequestOptions.DEFAULT.toBuilder() + .addHeader("Authorization", basicAuthHeaderValue("run_as_user", PASS)) + .addHeader("es-security-runas-user", "target_user") + ); final Response runAsResponse = client().performRequest(runAsSearchRequest); assertOK(runAsResponse); // Verify slow log shows both authenticating and effective users from querying cluster Map expectedRunAsAuthContext = Map.of( - "user.name", "run_as_user", // Authenticating user from querying cluster - "user.realm", "default_native", - "user.full_name", "Run As User", - "user.effective.name", "target_user", // Effective user from querying cluster - "user.effective.realm", "default_native", - "user.effective.full_name", "Target User", - "auth.type", "REALM" + "user.name", + "run_as_user", // Authenticating user from querying cluster + "user.realm", + "default_native", + "user.full_name", + "Run As User", + "user.effective.name", + "target_user", // Effective user from querying cluster + "user.effective.realm", + "default_native", + "user.effective.full_name", + "Target User", + "auth.type", + "REALM" ); verifySlowLogAuthenticationContext(expectedRunAsAuthContext); @@ -310,7 +323,7 @@ private void verifySlowLogAuthenticationContext(Map expectedAuth assertBusy(() -> { try (var slowLog = fulfillingCluster.getNodeLog(0, LogType.SEARCH_SLOW)) { final List lines = Streams.readAllLines(slowLog); - assert(!lines.isEmpty()); + assert (!lines.isEmpty()); // Get the most recent slow log entry String lastLogLine = lines.get(lines.size() - 1); @@ -334,14 +347,12 @@ private void verifySlowLogAuthenticationContext(Map expectedAuth } private Response performRequestWithSlowLogTestUser(final Request request) throws IOException { - request.setOptions(RequestOptions.DEFAULT.toBuilder() - .addHeader("Authorization", basicAuthHeaderValue("slow_log_test_user", PASS))); + request.setOptions(RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", basicAuthHeaderValue("slow_log_test_user", PASS))); return client().performRequest(request); } private Response performRequestWithApiKey(final Request request, final String encoded) throws IOException { - request.setOptions(RequestOptions.DEFAULT.toBuilder() - .addHeader("Authorization", "ApiKey " + encoded)); + request.setOptions(RequestOptions.DEFAULT.toBuilder().addHeader("Authorization", "ApiKey " + encoded)); return client().performRequest(request); } } From a5a38ff77765a36197e59c2b37a5bfe3dc7b3170 Mon Sep 17 00:00:00 2001 From: Graeme Mjehovich <59065536+gmjehovich@users.noreply.github.com> Date: Tue, 15 Jul 2025 20:25:01 -0400 Subject: [PATCH 07/13] integ test bugfix --- .../elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java index 3e90d53362401..dd4c5035a9911 100644 --- a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java @@ -195,9 +195,9 @@ public void testCrossClusterSlowLogAuthenticationContext() throws Exception { "user.name", "slow_log_test_user", // Original user from querying cluster "user.realm", - "default_native", // User's realm on querying cluster + "_es_api_key", // User's realm on querying cluster "user.full_name", - "Slow Log Test User", // User's full name + "Slow Log Test User", "auth.type", "API_KEY", // Authentication type "apikey.id", From 22f65e25d39538c323f00ac7dad7b2d1561b85c6 Mon Sep 17 00:00:00 2001 From: Graeme Mjehovich <59065536+gmjehovich@users.noreply.github.com> Date: Thu, 17 Jul 2025 14:43:40 -0400 Subject: [PATCH 08/13] Apply suggestions from code review Clean up inline comments Co-authored-by: Nikolaj Volgushev --- .../org/elasticsearch/xpack/security/Security.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 1249641524a46..6612afbebfd53 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -2404,15 +2404,12 @@ public Map getAuthContextForSlowLog() { Authentication authentication = this.securityContext.get().getAuthentication(); Map authContext = new HashMap<>(); - // Handle Cross-Cluster Access (RCS 2.0) scenario if (authentication.isCrossClusterAccess()) { Authentication originalAuthentication = Authentication.getAuthenticationFromCrossClusterAccessMetadata(authentication); - // Call the helper method for the originalAuthentication (from querying cluster) + // For RCS 2.0, we log the user from the querying cluster populateAuthContextMap(originalAuthentication, authContext); } - // Logic for obtaining non-cross-cluster access authentication information else { - // Call the helper method for the authentication itself populateAuthContextMap(authentication, authContext); } return authContext; @@ -2442,7 +2439,7 @@ private void populateAuthContextMap(Authentication auth, Map aut } // Only include effective user if different from authenticating user (run-as) - if (auth.isRunAs()) { // Use auth.isRunAs() for consistency + if (auth.isRunAs()) { if (effectiveSubject.getUser() != null) { authContext.put("user.effective.name", effectiveSubject.getUser().principal()); authContext.put("user.effective.realm", effectiveSubject.getRealm().getName()); @@ -2452,17 +2449,13 @@ private void populateAuthContextMap(Authentication auth, Map aut } } - // Auth type authContext.put("auth.type", auth.getAuthenticationType().name()); - // Add API key details if this authentication was an API key itself if (auth.isApiKey()) { - // These metadata fields are expected to be on the authenticating subject of the API key - // Use Objects.toString() for safety against null metadata values if not strictly guaranteed authContext.put("apikey.id", Objects.toString(authenticatingSubject.getMetadata().get(AuthenticationField.API_KEY_ID_KEY))); Object apiKeyName = authenticatingSubject.getMetadata().get(AuthenticationField.API_KEY_NAME_KEY); - if (apiKeyName != null) { // Name can be null for API keys, so check explicitly + if (apiKeyName != null) { authContext.put("apikey.name", apiKeyName.toString()); } } From 06375ed74287766b5964e6dcd386628d1bf251d5 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Thu, 17 Jul 2025 18:52:48 +0000 Subject: [PATCH 09/13] [CI] Auto commit changes from spotless --- .../main/java/org/elasticsearch/xpack/security/Security.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 6612afbebfd53..740ed2d439580 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -2408,8 +2408,7 @@ public Map getAuthContextForSlowLog() { Authentication originalAuthentication = Authentication.getAuthenticationFromCrossClusterAccessMetadata(authentication); // For RCS 2.0, we log the user from the querying cluster populateAuthContextMap(originalAuthentication, authContext); - } - else { + } else { populateAuthContextMap(authentication, authContext); } return authContext; From 631080f736491487987bc097560af32d5fa3ecf7 Mon Sep 17 00:00:00 2001 From: Graeme Mjehovich <59065536+gmjehovich@users.noreply.github.com> Date: Tue, 22 Jul 2025 21:14:38 -0400 Subject: [PATCH 10/13] Refactor SlowLog unit tests to use AuthenticationTestHelper --- ...> RemoteClusterSecuritySlowLogRestIT.java} | 7 +- .../xpack/security/SecurityTests.java | 362 +++++++++--------- 2 files changed, 183 insertions(+), 186 deletions(-) rename x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/{CcsSlowLogRestIT.java => RemoteClusterSecuritySlowLogRestIT.java} (98%) diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecuritySlowLogRestIT.java similarity index 98% rename from x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java rename to x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecuritySlowLogRestIT.java index dd4c5035a9911..c2b8eda84b6d8 100644 --- a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecuritySlowLogRestIT.java @@ -26,8 +26,11 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; /** @@ -43,7 +46,7 @@ * - user.realm: The realm of the original user on the querying cluster (e.g., "default_native") * - For run-as: Both authenticating and effective users from querying cluster */ -public class CcsSlowLogRestIT extends AbstractRemoteClusterSecurityTestCase { +public class RemoteClusterSecuritySlowLogRestIT extends AbstractRemoteClusterSecurityTestCase { private static final AtomicReference> API_KEY_MAP_REF = new AtomicReference<>(); @@ -323,7 +326,7 @@ private void verifySlowLogAuthenticationContext(Map expectedAuth assertBusy(() -> { try (var slowLog = fulfillingCluster.getNodeLog(0, LogType.SEARCH_SLOW)) { final List lines = Streams.readAllLines(slowLog); - assert (!lines.isEmpty()); + assertThat(lines, not(empty())); // Get the most recent slow log entry String lastLogLine = lines.get(lines.size() - 1); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 8f1a246cbf1d9..74bb760a12d39 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -85,7 +85,6 @@ import org.elasticsearch.xpack.core.security.action.service.TokenInfo; import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.AuthenticationField; -import org.elasticsearch.xpack.core.security.authc.AuthenticationResult; import org.elasticsearch.xpack.core.security.authc.AuthenticationTestHelper; import org.elasticsearch.xpack.core.security.authc.CrossClusterAccessSubjectInfo; import org.elasticsearch.xpack.core.security.authc.Realm; @@ -97,7 +96,6 @@ import org.elasticsearch.xpack.core.security.authc.support.AuthenticationContextSerializer; import org.elasticsearch.xpack.core.security.authc.support.CachingUsernamePasswordRealmSettings; import org.elasticsearch.xpack.core.security.authc.support.Hasher; -import org.elasticsearch.xpack.core.security.authz.RoleDescriptorsIntersection; import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.xpack.core.security.authz.permission.DocumentPermissions; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissions; @@ -134,6 +132,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiConsumer; @@ -1243,28 +1242,29 @@ public void testAuthContextForSlowLog_LocalAccess_OriginalRealmUser() throws Exc createComponents(Settings.EMPTY); AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); - User searchUser = new User( - "username", - new String[] { "user_role" }, - "User Full Name", - "user@example.com", - Collections.emptyMap(), - true - ); - Authentication.RealmRef userRealm = new Authentication.RealmRef("default_native", "native", "nodeName"); + Authentication realmAuth = AuthenticationTestHelper.builder().realm().build(false); - Authentication realmAuth = Authentication.newRealmAuthentication(searchUser, userRealm); serializer.writeToContext(realmAuth, threadContext); - Map authContextRealm = security.getAuthContextForSlowLog(); + Map authContext = security.getAuthContextForSlowLog(); + + assertNotNull(authContext); + + assertThat(authContext.get("user.name"), equalTo(realmAuth.getAuthenticatingSubject().getUser().principal())); + assertThat(authContext.get("user.realm"), equalTo(realmAuth.getAuthenticatingSubject().getRealm().getName())); - assertThat(authContextRealm.get("user.name"), equalTo("username")); - assertThat(authContextRealm.get("user.realm"), equalTo("default_native")); - assertThat(authContextRealm.get("user.full_name"), equalTo("User Full Name")); - assertThat(authContextRealm.get("auth.type"), equalTo(Authentication.AuthenticationType.REALM.name())); - assertFalse(authContextRealm.containsKey("user.effective.name")); - assertFalse(authContextRealm.containsKey("apikey.id")); - assertFalse(authContextRealm.containsKey("apikey.name")); + // user.full_name is randomized by AuthenticationTestHelper, can be null + if (realmAuth.getAuthenticatingSubject().getUser().fullName() != null) { + assertThat(authContext.get("user.full_name"), equalTo(realmAuth.getAuthenticatingSubject().getUser().fullName())); + } else { + assertFalse(authContext.containsKey("user.full_name")); + } + + assertThat(authContext.get("auth.type"), equalTo(realmAuth.getAuthenticationType().name())); + + assertFalse(authContext.containsKey("user.effective.name")); + assertFalse(authContext.containsKey("apikey.id")); + assertFalse(authContext.containsKey("apikey.name")); } /** @@ -1275,28 +1275,40 @@ public void testAuthContextForSlowLog_LocalAccess_ApiKeyAuthentication() throws createComponents(Settings.EMPTY); AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); - Map apiKeyMetadata = new HashMap<>(); - apiKeyMetadata.put(AuthenticationField.API_KEY_ID_KEY, "test_local_api_key_id_123"); - apiKeyMetadata.put(AuthenticationField.API_KEY_NAME_KEY, "MyLocalTestApiKey"); + String expectedApiKeyId = "test_local_api_key_id_123"; + Authentication apiKeyAuth = AuthenticationTestHelper.builder().apiKey(expectedApiKeyId).build(); - User apiKeyUser = new User("local_api_key_principal", new String[0], null, null, Collections.emptyMap(), true); + assertFalse(apiKeyAuth.isRunAs()); - AuthenticationResult apiKeyAuthResult = AuthenticationResult.success(apiKeyUser, apiKeyMetadata); + serializer.writeToContext(apiKeyAuth, threadContext); + Map authContext = security.getAuthContextForSlowLog(); + assertNotNull(authContext); - Authentication apiKeyAuth = Authentication.newApiKeyAuthentication(apiKeyAuthResult, "local_node_api_key_origin"); + assertThat(authContext.get("user.name"), equalTo(apiKeyAuth.getAuthenticatingSubject().getUser().principal())); + assertThat(authContext.get("user.realm"), equalTo(apiKeyAuth.getAuthenticatingSubject().getRealm().getName())); - serializer.writeToContext(apiKeyAuth, threadContext); + if (apiKeyAuth.getAuthenticatingSubject().getUser().fullName() != null) { + assertThat(authContext.get("user.full_name"), equalTo(apiKeyAuth.getAuthenticatingSubject().getUser().fullName())); + } else { + assertFalse(authContext.containsKey("user.full_name")); + } - Map authContext = security.getAuthContextForSlowLog(); + assertThat(authContext.get("auth.type"), equalTo(apiKeyAuth.getAuthenticationType().name())); + assertThat(authContext.get("apikey.id"), equalTo(expectedApiKeyId)); - assertNotNull(authContext); - assertThat(authContext.get("user.name"), equalTo("local_api_key_principal")); - assertThat(authContext.get("user.realm"), equalTo(AuthenticationField.API_KEY_REALM_NAME)); - assertFalse(authContext.containsKey("user.full_name")); - assertThat(authContext.get("auth.type"), equalTo(Authentication.AuthenticationType.API_KEY.name())); - assertThat(authContext.get("apikey.id"), equalTo("test_local_api_key_id_123")); - assertThat(authContext.get("apikey.name"), equalTo("MyLocalTestApiKey")); - assertFalse(authContext.containsKey("user.effective.name")); // Not a run-as scenario + if (apiKeyAuth.getAuthenticatingSubject().getMetadata().containsKey(AuthenticationField.API_KEY_NAME_KEY)) { + Object apiKeyName = apiKeyAuth.getAuthenticatingSubject().getMetadata().get(AuthenticationField.API_KEY_NAME_KEY); + if (apiKeyName != null) { + assertThat(authContext.get("apikey.name"), equalTo(Objects.toString(apiKeyName))); + } else { + assertFalse(authContext.containsKey("apikey.name")); + } + } else { + assertFalse(authContext.containsKey("apikey.name")); + } + + // This scenario is not a run-as, so these fields should be absent. + assertFalse(authContext.containsKey("user.effective.name")); assertFalse(authContext.containsKey("user.effective.realm")); } @@ -1308,7 +1320,6 @@ public void testAuthContextForSlowLog_LocalAccess_RunAsAuthentication() throws E createComponents(Settings.EMPTY); AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); - // Define the authenticating user User authenticatingUser = new User( "authenticating_user", new String[] { "admin" }, @@ -1317,12 +1328,8 @@ public void testAuthContextForSlowLog_LocalAccess_RunAsAuthentication() throws E Collections.emptyMap(), true ); - Authentication.RealmRef authenticatingRealm = new Authentication.RealmRef("local_file_realm", "file", "local_node_authenticators"); - Authentication baseAuth = Authentication.newRealmAuthentication(authenticatingUser, authenticatingRealm); - - // Define the effective user (the one being run-as) User effectiveUser = new User( "run_as_user", new String[] { "run_as" }, @@ -1331,10 +1338,16 @@ public void testAuthContextForSlowLog_LocalAccess_RunAsAuthentication() throws E Collections.emptyMap(), true ); - Authentication.RealmRef effectiveRealm = new Authentication.RealmRef("local_ldap_realm", "ldap", "local_node_ldap"); - Authentication runAsAuth = baseAuth.runAs(effectiveUser, effectiveRealm); + Authentication runAsAuth = AuthenticationTestHelper.builder() + .user(authenticatingUser) + .realmRef(authenticatingRealm) + .runAs() + .user(effectiveUser) + .realmRef(effectiveRealm) + .build(); + assertTrue(runAsAuth.isRunAs()); serializer.writeToContext(runAsAuth, threadContext); @@ -1342,17 +1355,29 @@ public void testAuthContextForSlowLog_LocalAccess_RunAsAuthentication() throws E Map authContext = security.getAuthContextForSlowLog(); assertNotNull(authContext); - assertThat(authContext.get("user.name"), equalTo("authenticating_user")); - assertThat(authContext.get("user.realm"), equalTo("local_file_realm")); - assertThat(authContext.get("user.full_name"), equalTo("Authenticating User")); - assertThat(authContext.get("user.effective.name"), equalTo("run_as_user")); - assertThat(authContext.get("user.effective.realm"), equalTo("local_ldap_realm")); - assertThat(authContext.get("user.effective.full_name"), equalTo("Run As User")); + // Assertions for the AUTHENTICATING user + assertThat(authContext.get("user.name"), equalTo(runAsAuth.getAuthenticatingSubject().getUser().principal())); + assertThat(authContext.get("user.realm"), equalTo(runAsAuth.getAuthenticatingSubject().getRealm().getName())); + if (runAsAuth.getAuthenticatingSubject().getUser().fullName() != null) { + assertThat(authContext.get("user.full_name"), equalTo(runAsAuth.getAuthenticatingSubject().getUser().fullName())); + } else { + assertFalse(authContext.containsKey("user.full_name")); + } - assertThat(authContext.get("auth.type"), equalTo(Authentication.AuthenticationType.REALM.name())); + // Assertions for the EFFECTIVE user + assertThat(authContext.get("user.effective.name"), equalTo(runAsAuth.getEffectiveSubject().getUser().principal())); + assertThat(authContext.get("user.effective.realm"), equalTo(runAsAuth.getEffectiveSubject().getRealm().getName())); + if (runAsAuth.getEffectiveSubject().getUser().fullName() != null) { + assertThat(authContext.get("user.effective.full_name"), equalTo(runAsAuth.getEffectiveSubject().getUser().fullName())); + } else { + assertFalse(authContext.containsKey("user.effective.full_name")); + } + + assertThat(authContext.get("auth.type"), equalTo(runAsAuth.getAuthenticationType().name())); assertFalse(authContext.containsKey("apikey.id")); assertFalse(authContext.containsKey("apikey.name")); + } /** @@ -1361,56 +1386,47 @@ public void testAuthContextForSlowLog_LocalAccess_RunAsAuthentication() throws E */ public void testAuthContextForSlowLog_CCA_OriginalRealmUser() throws Exception { createComponents(Settings.EMPTY); + AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); - // Create inner Authentication object for the 'remote_search_user' - User remoteSearchUser = new User( - "remote_search_user", - new String[] { "remote_role" }, - "Remote Search User Full Name", - "remote@example.com", - Collections.emptyMap(), - true - ); - Authentication.RealmRef remoteSearchRealm = new Authentication.RealmRef("default_native", "native", "node_name_querying_cluster"); - Authentication originalAuthentication = Authentication.newRealmAuthentication(remoteSearchUser, remoteSearchRealm); - - CrossClusterAccessSubjectInfo crossClusterAccessSubjectInfo = new CrossClusterAccessSubjectInfo( - originalAuthentication, - RoleDescriptorsIntersection.EMPTY - ); - - // Create outer Authentication object (the cross-cluster API key type) - User dummyApiKeyUser = new User("dummy_api_key_principal", new String[0], null, null, Collections.emptyMap(), true); + Authentication originalAuthentication = AuthenticationTestHelper.builder().realm().build(false); - Map authResultMetadata = new HashMap<>(); - authResultMetadata.put(AuthenticationField.API_KEY_ID_KEY, "test_api_key_unique_id_from_auth_result"); - authResultMetadata.put(AuthenticationField.API_KEY_NAME_KEY, "Test CCS API Key Name from AuthResult"); + assertFalse(originalAuthentication.isRunAs()); + assertFalse(originalAuthentication.isApiKey()); - AuthenticationResult apiAuthResult = AuthenticationResult.success(dummyApiKeyUser, authResultMetadata); + CrossClusterAccessSubjectInfo ccasi = AuthenticationTestHelper.randomCrossClusterAccessSubjectInfo(originalAuthentication); - Authentication baseApiKeyAuth = Authentication.newApiKeyAuthentication(apiAuthResult, "node_name_fulfilling_cluster"); + String outerApiKeyId = ESTestCase.randomAlphaOfLength(20); + User outerApiKeyUser = new User(outerApiKeyId, Strings.EMPTY_ARRAY); - Authentication outerCrossClusterAccessAuth = baseApiKeyAuth.toCrossClusterAccess(crossClusterAccessSubjectInfo); + Authentication outerCrossClusterAccessAuth = AuthenticationTestHelper.builder() + .user(outerApiKeyUser) + .crossClusterAccess(outerApiKeyId, ccasi) + .build(); assertTrue(outerCrossClusterAccessAuth.isCrossClusterAccess()); - assertThat(outerCrossClusterAccessAuth.getAuthenticatingSubject().getUser().principal(), equalTo("dummy_api_key_principal")); + assertThat(outerCrossClusterAccessAuth.getAuthenticatingSubject().getUser().principal(), equalTo(outerApiKeyId)); - assertNotNull( - outerCrossClusterAccessAuth.getAuthenticatingSubject() - .getMetadata() - .get(AuthenticationField.CROSS_CLUSTER_ACCESS_AUTHENTICATION_KEY) - ); - - AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); serializer.writeToContext(outerCrossClusterAccessAuth, threadContext); Map authContext = security.getAuthContextForSlowLog(); assertNotNull(authContext); - assertThat(authContext.get("user.name"), equalTo("remote_search_user")); - assertThat(authContext.get("user.realm"), equalTo("default_native")); - assertThat(authContext.get("user.full_name"), equalTo("Remote Search User Full Name")); - assertThat(authContext.get("auth.type"), equalTo(Authentication.AuthenticationType.REALM.name())); + + assertThat(authContext.get("user.name"), equalTo(originalAuthentication.getAuthenticatingSubject().getUser().principal())); + assertThat(authContext.get("user.realm"), equalTo(originalAuthentication.getAuthenticatingSubject().getRealm().getName())); + + if (originalAuthentication.getAuthenticatingSubject().getUser().fullName() != null) { + assertThat(authContext.get("user.full_name"), equalTo(originalAuthentication.getAuthenticatingSubject().getUser().fullName())); + } else { + assertFalse(authContext.containsKey("user.full_name")); + } + + assertThat(authContext.get("auth.type"), equalTo(originalAuthentication.getAuthenticationType().name())); + + assertFalse(authContext.containsKey("user.effective.name")); + assertFalse(authContext.containsKey("user.effective.realm")); + assertFalse(authContext.containsKey("apikey.id")); + assertFalse(authContext.containsKey("apikey.name")); } /** @@ -1421,52 +1437,59 @@ public void testAuthContextForSlowLog_CCA_OriginalApiKeyUser() throws Exception createComponents(Settings.EMPTY); AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); - // Original user authenticated via an API Key on the querying cluster - User originalApiKeyUser = new User("original_api_key_principal", new String[0], null, null, Collections.emptyMap(), true); - - Map originalApiKeyMetadata = new HashMap<>(); - originalApiKeyMetadata.put(AuthenticationField.API_KEY_ID_KEY, "original_api_key_id_xyz"); - originalApiKeyMetadata.put(AuthenticationField.API_KEY_NAME_KEY, "Original Remote API Key Name"); + Authentication originalAuthentication = AuthenticationTestHelper.builder().apiKey().build(); - Authentication originalAuthenticationApiKey = Authentication.newApiKeyAuthentication( - AuthenticationResult.success(originalApiKeyUser, originalApiKeyMetadata), - "node_querying_apikey" - ); + assertFalse(originalAuthentication.isRunAs()); + assertTrue(originalAuthentication.isApiKey()); - // Wrap the original Authentication - CrossClusterAccessSubjectInfo ccasiApiKey = new CrossClusterAccessSubjectInfo( - originalAuthenticationApiKey, - RoleDescriptorsIntersection.EMPTY - ); + CrossClusterAccessSubjectInfo ccasi = AuthenticationTestHelper.randomCrossClusterAccessSubjectInfo(originalAuthentication); - User dummyApiKeyUserForApiKey = new User( - "dummy_api_key_id_apikey", - new String[0], - null, - null, - Map.of(AuthenticationField.API_KEY_ID_KEY, "api_id_apikey"), - true - ); + String outerApiKeyId = ESTestCase.randomAlphaOfLength(20); + User outerApiKeyUser = new User(outerApiKeyId, Strings.EMPTY_ARRAY); - Authentication baseApiKeyAuthApiKey = Authentication.newApiKeyAuthentication( - AuthenticationResult.success(dummyApiKeyUserForApiKey, Map.of(AuthenticationField.API_KEY_ID_KEY, "api_id_apikey")), - "node_fulfilling_apikey" - ); + Authentication outerCrossClusterAccessAuth = AuthenticationTestHelper.builder() + .user(outerApiKeyUser) + .crossClusterAccess(outerApiKeyId, ccasi) + .build(); - Authentication outerCrossClusterAccessAuthApiKey = baseApiKeyAuthApiKey.toCrossClusterAccess(ccasiApiKey); + assertTrue(outerCrossClusterAccessAuth.isCrossClusterAccess()); + assertThat(outerCrossClusterAccessAuth.getAuthenticatingSubject().getUser().principal(), equalTo(outerApiKeyId)); - serializer.writeToContext(outerCrossClusterAccessAuthApiKey, threadContext); + serializer.writeToContext(outerCrossClusterAccessAuth, threadContext); Map authContext = security.getAuthContextForSlowLog(); assertNotNull(authContext); - assertThat(authContext.get("user.name"), equalTo("original_api_key_principal")); - // API key realm name from the original authentication (AuthenticationField.API_KEY_REALM_NAME) - assertThat(authContext.get("user.realm"), equalTo(AuthenticationField.API_KEY_REALM_NAME)); - assertThat(authContext.get("auth.type"), equalTo(Authentication.AuthenticationType.API_KEY.name())); - assertThat(authContext.get("apikey.id"), equalTo("original_api_key_id_xyz")); - assertThat(authContext.get("apikey.name"), equalTo("Original Remote API Key Name")); + + assertThat(authContext.get("user.name"), equalTo(originalAuthentication.getAuthenticatingSubject().getUser().principal())); + assertThat(authContext.get("user.realm"), equalTo(originalAuthentication.getAuthenticatingSubject().getRealm().getName())); + + if (originalAuthentication.getAuthenticatingSubject().getUser().fullName() != null) { + assertThat(authContext.get("user.full_name"), equalTo(originalAuthentication.getAuthenticatingSubject().getUser().fullName())); + } else { + assertFalse(authContext.containsKey("user.full_name")); + } + + assertThat(authContext.get("auth.type"), equalTo(originalAuthentication.getAuthenticationType().name())); + + // Assert API key details from the inner API Key user + assertThat( + authContext.get("apikey.id"), + equalTo(originalAuthentication.getAuthenticatingSubject().getMetadata().get(AuthenticationField.API_KEY_ID_KEY)) + ); + if (originalAuthentication.getAuthenticatingSubject().getMetadata().containsKey(AuthenticationField.API_KEY_NAME_KEY)) { + Object apiKeyName = originalAuthentication.getAuthenticatingSubject().getMetadata().get(AuthenticationField.API_KEY_NAME_KEY); + if (apiKeyName != null) { + assertThat(authContext.get("apikey.name"), equalTo(Objects.toString(apiKeyName))); + } else { + assertFalse(authContext.containsKey("apikey.name")); + } + } else { + assertFalse(authContext.containsKey("apikey.name")); + } + assertFalse(authContext.containsKey("user.effective.name")); + assertFalse(authContext.containsKey("user.effective.realm")); } /** @@ -1477,80 +1500,51 @@ public void testAuthContextForSlowLog_CCA_OriginalRunAsUser() throws Exception { createComponents(Settings.EMPTY); AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); - // Authenticating user on querying cluster (who performs the run-as) - User authenticatingRemoteUser = new User( - "authenticating_remote", - new String[] { "power_user" }, - "Authenticating Remote User", - null, - Collections.emptyMap(), - true - ); - - Authentication.RealmRef authenticatingRemoteRealm = new Authentication.RealmRef("remote_auth_realm", "ldap", "node_querying_auth"); - - Authentication baseAuthenticationRemote = Authentication.newRealmAuthentication( - authenticatingRemoteUser, - authenticatingRemoteRealm - ); - - // Effective user (the one being run-as) on querying cluster - User effectiveRemoteUser = new User( - "effective_remote", - new String[] { "readonly" }, - "Effective Remote User", - null, - Collections.emptyMap(), - true - ); - - Authentication.RealmRef effectiveRemoteRealm = new Authentication.RealmRef( - "remote_effective_realm", - "file", - "node_querying_effective" - ); + Authentication originalAuthentication = AuthenticationTestHelper.builder().runAs().build(); - // Create the run-as authentication for the original user - Authentication originalAuthenticationRunAs = baseAuthenticationRemote.runAs(effectiveRemoteUser, effectiveRemoteRealm); - assertTrue(originalAuthenticationRunAs.isRunAs()); // Verify it's a run-as type + assertTrue(originalAuthentication.isRunAs()); + assertFalse(originalAuthentication.isApiKey()); - // Build the CrossClusterAccessSubjectInfo wrapping the original Authentication - CrossClusterAccessSubjectInfo ccasiRunAs = new CrossClusterAccessSubjectInfo( - originalAuthenticationRunAs, - RoleDescriptorsIntersection.EMPTY - ); + CrossClusterAccessSubjectInfo ccasi = AuthenticationTestHelper.randomCrossClusterAccessSubjectInfo(originalAuthentication); - User dummyApiKeyUserForRunAs = new User( - "dummy_api_key_id_runas", - new String[0], - null, - null, - Map.of(AuthenticationField.API_KEY_ID_KEY, "api_id_runas"), - true - ); + String outerApiKeyId = ESTestCase.randomAlphaOfLength(20); + User outerApiKeyUser = new User(outerApiKeyId, Strings.EMPTY_ARRAY); - Authentication baseApiKeyAuthRunAs = Authentication.newApiKeyAuthentication( - AuthenticationResult.success(dummyApiKeyUserForRunAs, Map.of(AuthenticationField.API_KEY_ID_KEY, "api_id_runas")), - "node_fulfilling_runas" - ); + Authentication outerCrossClusterAccessAuth = AuthenticationTestHelper.builder() + .user(outerApiKeyUser) + .crossClusterAccess(outerApiKeyId, ccasi) + .build(); - // Convert to the outer Cross-Cluster Access Authentication (what the fulfilling cluster sees) - Authentication outerCrossClusterAccessAuthRunAs = baseApiKeyAuthRunAs.toCrossClusterAccess(ccasiRunAs); + assertTrue(outerCrossClusterAccessAuth.isCrossClusterAccess()); + assertThat(outerCrossClusterAccessAuth.getAuthenticatingSubject().getUser().principal(), equalTo(outerApiKeyId)); - serializer.writeToContext(outerCrossClusterAccessAuthRunAs, threadContext); + serializer.writeToContext(outerCrossClusterAccessAuth, threadContext); Map authContext = security.getAuthContextForSlowLog(); assertNotNull(authContext); - assertThat(authContext.get("user.name"), equalTo("authenticating_remote")); - assertThat(authContext.get("user.realm"), equalTo("remote_auth_realm")); - assertThat(authContext.get("user.full_name"), equalTo("Authenticating Remote User")); + // Assertions for the fields derived from the inner Run-as user + assertThat(authContext.get("user.name"), equalTo(originalAuthentication.getAuthenticatingSubject().getUser().principal())); + assertThat(authContext.get("user.realm"), equalTo(originalAuthentication.getAuthenticatingSubject().getRealm().getName())); + if (originalAuthentication.getAuthenticatingSubject().getUser().fullName() != null) { + assertThat(authContext.get("user.full_name"), equalTo(originalAuthentication.getAuthenticatingSubject().getUser().fullName())); + } else { + assertFalse(authContext.containsKey("user.full_name")); + } + + assertThat(authContext.get("user.effective.name"), equalTo(originalAuthentication.getEffectiveSubject().getUser().principal())); + assertThat(authContext.get("user.effective.realm"), equalTo(originalAuthentication.getEffectiveSubject().getRealm().getName())); + if (originalAuthentication.getEffectiveSubject().getUser().fullName() != null) { + assertThat( + authContext.get("user.effective.full_name"), + equalTo(originalAuthentication.getEffectiveSubject().getUser().fullName()) + ); + } else { + assertFalse(authContext.containsKey("user.effective.full_name")); + } - assertThat(authContext.get("user.effective.name"), equalTo("effective_remote")); - assertThat(authContext.get("user.effective.realm"), equalTo("remote_effective_realm")); - assertThat(authContext.get("user.effective.full_name"), equalTo("Effective Remote User")); + assertThat(authContext.get("auth.type"), equalTo(originalAuthentication.getAuthenticationType().name())); - assertThat(authContext.get("auth.type"), equalTo(Authentication.AuthenticationType.REALM.name())); assertFalse(authContext.containsKey("apikey.id")); assertFalse(authContext.containsKey("apikey.name")); } From 21c47a6ce8245891c41ce0931f6abc3dae143630 Mon Sep 17 00:00:00 2001 From: Graeme Mjehovich <59065536+gmjehovich@users.noreply.github.com> Date: Tue, 22 Jul 2025 21:27:03 -0400 Subject: [PATCH 11/13] Refactor populateAuthContextMap to create auth context map --- .../org/elasticsearch/xpack/security/Security.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 740ed2d439580..561f4e31e6a67 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -2402,14 +2402,14 @@ private void reloadRemoteClusterCredentials(Settings settingsWithKeystore) { public Map getAuthContextForSlowLog() { if (this.securityContext.get() != null && this.securityContext.get().getAuthentication() != null) { Authentication authentication = this.securityContext.get().getAuthentication(); - Map authContext = new HashMap<>(); + Map authContext; if (authentication.isCrossClusterAccess()) { Authentication originalAuthentication = Authentication.getAuthenticationFromCrossClusterAccessMetadata(authentication); // For RCS 2.0, we log the user from the querying cluster - populateAuthContextMap(originalAuthentication, authContext); + authContext = createAuthContextMap(originalAuthentication); } else { - populateAuthContextMap(authentication, authContext); + authContext = createAuthContextMap(authentication); } return authContext; } @@ -2422,9 +2422,10 @@ public Map getAuthContextForSlowLog() { * original authentication in cross-cluster access scenarios. * * @param auth The Authentication object to extract details from. - * @param authContext The map to populate with authentication details. */ - private void populateAuthContextMap(Authentication auth, Map authContext) { + private Map createAuthContextMap(Authentication auth) { + Map authContext = new HashMap<>(); + Subject authenticatingSubject = auth.getAuthenticatingSubject(); Subject effectiveSubject = auth.getEffectiveSubject(); @@ -2458,6 +2459,7 @@ private void populateAuthContextMap(Authentication auth, Map aut authContext.put("apikey.name", apiKeyName.toString()); } } + return authContext; } static final class ValidateLicenseForFIPS implements BiConsumer { From 6755ccb4dac202c91d04816f771acd0a58bfb86f Mon Sep 17 00:00:00 2001 From: Graeme Mjehovich <59065536+gmjehovich@users.noreply.github.com> Date: Thu, 24 Jul 2025 14:30:26 -0400 Subject: [PATCH 12/13] Remove javadocs --- .../RemoteClusterSecuritySlowLogRestIT.java | 17 ----------------- .../xpack/security/SecurityTests.java | 16 ---------------- 2 files changed, 33 deletions(-) diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecuritySlowLogRestIT.java b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecuritySlowLogRestIT.java index c2b8eda84b6d8..40bf4a13e5696 100644 --- a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecuritySlowLogRestIT.java +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecuritySlowLogRestIT.java @@ -33,19 +33,6 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; -/** - * Integration test for verifying that slow log authentication context contains - * the correct user information for cross-cluster access scenarios. - * - * This test verifies that when cross-cluster searches are performed, the slow logs - * on the fulfilling cluster contain the authentication context of the ORIGINAL user - * from the querying cluster, not just the cross-cluster access API key. - * - * The key verification is that slow logs should show: - * - user.name: The actual user from the querying cluster (e.g., "slow_log_test_user") - * - user.realm: The realm of the original user on the querying cluster (e.g., "default_native") - * - For run-as: Both authenticating and effective users from querying cluster - */ public class RemoteClusterSecuritySlowLogRestIT extends AbstractRemoteClusterSecurityTestCase { private static final AtomicReference> API_KEY_MAP_REF = new AtomicReference<>(); @@ -318,10 +305,6 @@ public void testRunAsUserInCrossClusterSlowLog() throws Exception { } } - /** - * Verifies that the slow logs on the fulfilling cluster contain the expected - * authentication context from the original user on the querying cluster. - */ private void verifySlowLogAuthenticationContext(Map expectedAuthContext) throws Exception { assertBusy(() -> { try (var slowLog = fulfillingCluster.getNodeLog(0, LogType.SEARCH_SLOW)) { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 74bb760a12d39..26bdf04a8ef8c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -1312,10 +1312,6 @@ public void testAuthContextForSlowLog_LocalAccess_ApiKeyAuthentication() throws assertFalse(authContext.containsKey("user.effective.realm")); } - /** - * Tests getAuthContextForSlowLog for a Run-as authentication scenario. - * Covers a part of the 'else' branch where authentication is NOT cross-cluster. - */ public void testAuthContextForSlowLog_LocalAccess_RunAsAuthentication() throws Exception { createComponents(Settings.EMPTY); AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); @@ -1380,10 +1376,6 @@ public void testAuthContextForSlowLog_LocalAccess_RunAsAuthentication() throws E } - /** - * Tests getAuthContextForSlowLog for a Cross-Cluster Access scenario - * where the original user on the querying cluster authenticated via a Realm. - */ public void testAuthContextForSlowLog_CCA_OriginalRealmUser() throws Exception { createComponents(Settings.EMPTY); AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); @@ -1429,10 +1421,6 @@ public void testAuthContextForSlowLog_CCA_OriginalRealmUser() throws Exception { assertFalse(authContext.containsKey("apikey.name")); } - /** - * Tests getAuthContextForSlowLog for a Cross-Cluster Access scenario - * where the original user on the querying cluster authenticated via an API Key. - */ public void testAuthContextForSlowLog_CCA_OriginalApiKeyUser() throws Exception { createComponents(Settings.EMPTY); AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); @@ -1492,10 +1480,6 @@ public void testAuthContextForSlowLog_CCA_OriginalApiKeyUser() throws Exception assertFalse(authContext.containsKey("user.effective.realm")); } - /** - * Tests getAuthContextForSlowLog for a Cross-Cluster Access scenario - * where the original user on the querying cluster was a Run-As user. - */ public void testAuthContextForSlowLog_CCA_OriginalRunAsUser() throws Exception { createComponents(Settings.EMPTY); AuthenticationContextSerializer serializer = new AuthenticationContextSerializer(); From b8f2d20fe33acabf85ef5e22f195e87d951d0b35 Mon Sep 17 00:00:00 2001 From: Graeme Mjehovich <59065536+gmjehovich@users.noreply.github.com> Date: Thu, 24 Jul 2025 14:35:50 -0400 Subject: [PATCH 13/13] Clean up comments --- .../RemoteClusterSecuritySlowLogRestIT.java | 19 +++++++------------ .../xpack/security/Security.java | 7 ------- .../xpack/security/SecurityTests.java | 4 ---- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecuritySlowLogRestIT.java b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecuritySlowLogRestIT.java index 40bf4a13e5696..c08797b6f9f21 100644 --- a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecuritySlowLogRestIT.java +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecuritySlowLogRestIT.java @@ -26,7 +26,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasKey; @@ -180,20 +179,19 @@ public void testCrossClusterSlowLogAuthenticationContext() throws Exception { assertOK(searchResponse); // Verify slow log contains correct authentication context from the original user - // The key test: slow logs should show the original user from querying cluster Map expectedAuthContext = Map.of( "user.name", - "slow_log_test_user", // Original user from querying cluster + "slow_log_test_user", "user.realm", - "_es_api_key", // User's realm on querying cluster + "_es_api_key", "user.full_name", "Slow Log Test User", "auth.type", - "API_KEY", // Authentication type + "API_KEY", "apikey.id", - apiKeyId, // API key from querying cluster + apiKeyId, "apikey.name", - "slow_log_test_api_key" // API key name + "slow_log_test_api_key" ); verifySlowLogAuthenticationContext(expectedAuthContext); @@ -205,7 +203,6 @@ public void testRunAsUserInCrossClusterSlowLog() throws Exception { // Fulfilling cluster setup { - // Create an index for run-as testing with slow log enabled final Request createIndexRequest = new Request("PUT", "/run_as_test"); createIndexRequest.setJsonEntity(""" { @@ -283,16 +280,15 @@ public void testRunAsUserInCrossClusterSlowLog() throws Exception { final Response runAsResponse = client().performRequest(runAsSearchRequest); assertOK(runAsResponse); - // Verify slow log shows both authenticating and effective users from querying cluster Map expectedRunAsAuthContext = Map.of( "user.name", - "run_as_user", // Authenticating user from querying cluster + "run_as_user", "user.realm", "default_native", "user.full_name", "Run As User", "user.effective.name", - "target_user", // Effective user from querying cluster + "target_user", "user.effective.realm", "default_native", "user.effective.full_name", @@ -315,7 +311,6 @@ private void verifySlowLogAuthenticationContext(Map expectedAuth String lastLogLine = lines.get(lines.size() - 1); Map logEntry = XContentHelper.convertToMap(XContentType.JSON.xContent(), lastLogLine, true); - // Verify that the log entry contains the expected authentication context for (Map.Entry expectedEntry : expectedAuthContext.entrySet()) { assertThat( "Slow log should contain " + expectedEntry.getKey() + " with value " + expectedEntry.getValue(), diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java index 561f4e31e6a67..2b927b531b5d6 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/Security.java @@ -2416,13 +2416,6 @@ public Map getAuthContextForSlowLog() { return Map.of(); } - /** - * Helper method to populate authentication context fields for slow logs. - * This logic is common for both direct authentications and the nested - * original authentication in cross-cluster access scenarios. - * - * @param auth The Authentication object to extract details from. - */ private Map createAuthContextMap(Authentication auth) { Map authContext = new HashMap<>(); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java index 26bdf04a8ef8c..bf82846a81da4 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/SecurityTests.java @@ -1267,10 +1267,6 @@ public void testAuthContextForSlowLog_LocalAccess_OriginalRealmUser() throws Exc assertFalse(authContext.containsKey("apikey.name")); } - /** - * Tests getAuthContextForSlowLog for an API Key authentication scenario. - * Covers a part of the 'else' branch where authentication is NOT cross-cluster. - */ public void testAuthContextForSlowLog_LocalAccess_ApiKeyAuthentication() throws Exception { createComponents(Settings.EMPTY); AuthenticationContextSerializer serializer = new AuthenticationContextSerializer();