- 
                Notifications
    
You must be signed in to change notification settings  - Fork 25.6k
 
Correct slow log user for RCS 2.0 #130140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Merged
      
      
    
  
     Merged
                    Changes from 13 commits
      Commits
    
    
            Show all changes
          
          
            22 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      2220731
              
                Show Original User in Slow Logs for RCS 2.0
              
              
                gmjehovich 0e6029d
              
                fix commenting
              
              
                gmjehovich feead9d
              
                Update docs/changelog/130140.yaml
              
              
                gmjehovich eb2006e
              
                [CI] Auto commit changes from spotless
              
              
                 9900f38
              
                Merge branch 'main' into slow_log_rcs
              
              
                gmjehovich d3a7cc2
              
                Add Integration Tests for Cross Cluster Search Slow Logs
              
              
                gmjehovich 0f91e78
              
                Merge branch 'main' into slow_log_rcs
              
              
                gmjehovich fd7e9e6
              
                [CI] Auto commit changes from spotless
              
              
                 f0312da
              
                Merge branch 'main' into slow_log_rcs
              
              
                gmjehovich a5a38ff
              
                integ test bugfix
              
              
                gmjehovich b21c76b
              
                Merge branch 'main' into slow_log_rcs
              
              
                gmjehovich e04f43c
              
                Merge branch 'main' into slow_log_rcs
              
              
                gmjehovich 777e4ff
              
                Merge branch 'main' into slow_log_rcs
              
              
                gmjehovich 22f65e2
              
                Apply suggestions from code review
              
              
                gmjehovich 06375ed
              
                [CI] Auto commit changes from spotless
              
              
                 b58765a
              
                Merge branch 'main' into slow_log_rcs
              
              
                gmjehovich 61153f5
              
                Merge branch 'main' into slow_log_rcs
              
              
                gmjehovich 631080f
              
                Refactor SlowLog unit tests to use AuthenticationTestHelper
              
              
                gmjehovich 21c47a6
              
                Refactor populateAuthContextMap to create auth context map
              
              
                gmjehovich 109e38b
              
                Merge branch 'elastic:main' into slow_log_rcs
              
              
                gmjehovich 6755ccb
              
                Remove javadocs
              
              
                gmjehovich b8f2d20
              
                Clean up comments
              
              
                gmjehovich File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| pr: 130140 | ||
| summary: Correct slow log user for RCS 2.0 | ||
| area: Authentication | ||
| type: enhancement | ||
| issues: [] | 
        
          
          
            358 changes: 358 additions & 0 deletions
          
          358 
        
  ...cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/CcsSlowLogRestIT.java
  
  
      
      
   
        
      
      
    
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,358 @@ | ||
| /* | ||
| * 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.xcontent.XContentHelper; | ||
| 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 { | ||
                
      
                  gmjehovich marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| private static final AtomicReference<Map<String, Object>> 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<String, Object> 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<String, Object> expectedAuthContext = Map.of( | ||
| "user.name", | ||
| "slow_log_test_user", // Original user from querying cluster | ||
| "user.realm", | ||
| "_es_api_key", // User's realm on querying cluster | ||
| "user.full_name", | ||
| "Slow Log Test User", | ||
| "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<String, Object> 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<String, Object> expectedAuthContext) throws Exception { | ||
| assertBusy(() -> { | ||
| try (var slowLog = fulfillingCluster.getNodeLog(0, LogType.SEARCH_SLOW)) { | ||
| final List<String> lines = Streams.readAllLines(slowLog); | ||
| assert (!lines.isEmpty()); | ||
                
      
                  gmjehovich marked this conversation as resolved.
               
              
                Outdated
          
            Show resolved
            Hide resolved
         | 
||
| 
     | 
||
| // Get the most recent slow log entry | ||
| String lastLogLine = lines.get(lines.size() - 1); | ||
| Map<String, Object> logEntry = XContentHelper.convertToMap(XContentType.JSON.xContent(), lastLogLine, true); | ||
| 
     | 
||
| // Verify that the log entry contains the expected authentication context | ||
| for (Map.Entry<String, Object> 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); | ||
| } | ||
| } | ||
      
      Oops, something went wrong.
        
    
  
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.