Bläddra i källkod

Fix isApiKey test and apply it consistently (#84396)

Creating tokens using API keys is not properly supported till #80926.
Previously the created token always has no previlege. Now the token has
the same privilege as the API key itself (similar to user created
tokens). Authenticating using the token is considered equivalent to the
API key itself. Therefore the "isApiKey" check needs to be updated to
cater for both authentications of API key itself and the token created
by the API key.

This PR updates the isApiKey check and apply it consistently to ensure
the behaviour is consistent between an API key and a token created by
it.

The only exception is for supporting run-as. API key itself can run-as
another user. But a token created by the API key cannot perform run-as
(#84336) similar to how user/token works.
Yang Wang 3 år sedan
förälder
incheckning
a6340a557a

+ 21 - 8
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authc/Authentication.java

@@ -229,8 +229,14 @@ public class Authentication implements ToXContentObject {
         return ServiceAccountSettings.REALM_TYPE.equals(getAuthenticatedBy().getType());
         return ServiceAccountSettings.REALM_TYPE.equals(getAuthenticatedBy().getType());
     }
     }
 
 
-    public boolean isAuthenticatedWithApiKey() {
-        return AuthenticationType.API_KEY.equals(getAuthenticationType());
+    /**
+     * Whether the authenticating user is an API key, including a simple API key or a token created by an API key.
+     * @return
+     */
+    public boolean isAuthenticatedAsApiKey() {
+        final boolean result = AuthenticationField.API_KEY_REALM_TYPE.equals(getAuthenticatedBy().getType());
+        assert false == result || AuthenticationField.API_KEY_REALM_NAME.equals(getAuthenticatedBy().getName());
+        return result;
     }
     }
 
 
     public boolean isAuthenticatedAnonymously() {
     public boolean isAuthenticatedAnonymously() {
@@ -245,14 +251,20 @@ public class Authentication implements ToXContentObject {
      * Authenticate with a service account and no run-as
      * Authenticate with a service account and no run-as
      */
      */
     public boolean isServiceAccount() {
     public boolean isServiceAccount() {
-        return isAuthenticatedWithServiceAccount() && false == getUser().isRunAs();
+        final boolean result = ServiceAccountSettings.REALM_TYPE.equals(getSourceRealm().getType());
+        assert false == result || ServiceAccountSettings.REALM_NAME.equals(getSourceRealm().getName())
+            : "service account realm name mismatch";
+        return result;
     }
     }
 
 
     /**
     /**
-     * Authenticated with an API key and no run-as
+     * Whether the effective user is an API key, this including a simple API key authentication
+     * or a token created by the API key.
      */
      */
     public boolean isApiKey() {
     public boolean isApiKey() {
-        return isAuthenticatedWithApiKey() && false == getUser().isRunAs();
+        final boolean result = AuthenticationField.API_KEY_REALM_TYPE.equals(getSourceRealm().getType());
+        assert false == result || AuthenticationField.API_KEY_REALM_NAME.equals(getSourceRealm().getName()) : "api key realm name mismatch";
+        return result;
     }
     }
 
 
     /**
     /**
@@ -419,7 +431,7 @@ public class Authentication implements ToXContentObject {
     }
     }
 
 
     private void assertApiKeyMetadata() {
     private void assertApiKeyMetadata() {
-        assert (false == isAuthenticatedWithApiKey()) || (this.metadata.get(AuthenticationField.API_KEY_ID_KEY) != null)
+        assert (false == isAuthenticatedAsApiKey()) || (this.metadata.get(AuthenticationField.API_KEY_ID_KEY) != null)
             : "API KEY authentication requires metadata to contain API KEY id, and the value must be non-null.";
             : "API KEY authentication requires metadata to contain API KEY id, and the value must be non-null.";
     }
     }
 
 
@@ -695,8 +707,9 @@ public class Authentication implements ToXContentObject {
     @SuppressWarnings("unchecked")
     @SuppressWarnings("unchecked")
     private static Map<String, Object> maybeRewriteMetadataForApiKeyRoleDescriptors(Version streamVersion, Authentication authentication) {
     private static Map<String, Object> maybeRewriteMetadataForApiKeyRoleDescriptors(Version streamVersion, Authentication authentication) {
         Map<String, Object> metadata = authentication.getMetadata();
         Map<String, Object> metadata = authentication.getMetadata();
-        // If authentication type is API key, regardless whether it has run-as, the metadata must contain API key role descriptors
-        if (authentication.isAuthenticatedWithApiKey()) {
+        // If authentication user is an API key or a token created by an API key,
+        // regardless whether it has run-as, the metadata must contain API key role descriptors
+        if (authentication.isAuthenticatedAsApiKey()) {
             assert metadata.containsKey(AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY)
             assert metadata.containsKey(AuthenticationField.API_KEY_ROLE_DESCRIPTORS_KEY)
                 : "metadata must contain role descriptor for API key authentication";
                 : "metadata must contain role descriptor for API key authentication";
             assert metadata.containsKey(AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY)
             assert metadata.containsKey(AuthenticationField.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY)

+ 2 - 1
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageOwnApiKeyClusterPrivilegeTests.java

@@ -187,8 +187,9 @@ public class ManageOwnApiKeyClusterPrivilegeTests extends ESTestCase {
         when(authentication.getSourceRealm()).thenReturn(authenticatedBy);
         when(authentication.getSourceRealm()).thenReturn(authenticatedBy);
         when(authentication.getAuthenticationType()).thenReturn(authenticationType);
         when(authentication.getAuthenticationType()).thenReturn(authenticationType);
         when(authenticatedBy.getName()).thenReturn(realmName);
         when(authenticatedBy.getName()).thenReturn(realmName);
+        when(authenticatedBy.getType()).thenReturn(realmName);
         when(authentication.getMetadata()).thenReturn(metadata);
         when(authentication.getMetadata()).thenReturn(metadata);
-        when(authentication.isAuthenticatedWithApiKey()).thenCallRealMethod();
+        when(authentication.isAuthenticatedAsApiKey()).thenCallRealMethod();
         when(authentication.isApiKey()).thenCallRealMethod();
         when(authentication.isApiKey()).thenCallRealMethod();
         return authentication;
         return authentication;
     }
     }

+ 7 - 1
x-pack/plugin/identity-provider/src/test/java/org/elasticsearch/xpack/idp/action/TransportSamlInitiateSingleSignOnActionTests.java

@@ -6,6 +6,7 @@
  */
  */
 package org.elasticsearch.xpack.idp.action;
 package org.elasticsearch.xpack.idp.action;
 
 
+import org.elasticsearch.Version;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.action.support.PlainActionFuture;
 import org.elasticsearch.action.support.PlainActionFuture;
@@ -19,6 +20,7 @@ import org.elasticsearch.transport.Transport;
 import org.elasticsearch.transport.TransportService;
 import org.elasticsearch.transport.TransportService;
 import org.elasticsearch.xpack.core.security.SecurityContext;
 import org.elasticsearch.xpack.core.security.SecurityContext;
 import org.elasticsearch.xpack.core.security.authc.Authentication;
 import org.elasticsearch.xpack.core.security.authc.Authentication;
+import org.elasticsearch.xpack.core.security.authc.AuthenticationField;
 import org.elasticsearch.xpack.core.security.authc.support.SecondaryAuthentication;
 import org.elasticsearch.xpack.core.security.authc.support.SecondaryAuthentication;
 import org.elasticsearch.xpack.core.security.user.User;
 import org.elasticsearch.xpack.core.security.user.User;
 import org.elasticsearch.xpack.idp.privileges.ServiceProviderPrivileges;
 import org.elasticsearch.xpack.idp.privileges.ServiceProviderPrivileges;
@@ -40,6 +42,7 @@ import java.net.URL;
 import java.time.Duration;
 import java.time.Duration;
 import java.util.Collections;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
 import java.util.Set;
 
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.containsString;
@@ -162,7 +165,10 @@ public class TransportSamlInitiateSingleSignOnActionTests extends IdpSamlTestCas
                         true
                         true
                     ),
                     ),
                     new Authentication.RealmRef("_es_api_key", "_es_api_key", "node_name"),
                     new Authentication.RealmRef("_es_api_key", "_es_api_key", "node_name"),
-                    new Authentication.RealmRef("_es_api_key", "_es_api_key", "node_name")
+                    new Authentication.RealmRef("_es_api_key", "_es_api_key", "node_name"),
+                    Version.CURRENT,
+                    Authentication.AuthenticationType.API_KEY,
+                    Map.of(AuthenticationField.API_KEY_ID_KEY, randomAlphaOfLength(20))
                 )
                 )
             ).writeToContext(threadContext);
             ).writeToContext(threadContext);
         }
         }

+ 18 - 2
x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java

@@ -52,6 +52,9 @@ import org.elasticsearch.xpack.core.security.action.apikey.GetApiKeyResponse;
 import org.elasticsearch.xpack.core.security.action.apikey.InvalidateApiKeyAction;
 import org.elasticsearch.xpack.core.security.action.apikey.InvalidateApiKeyAction;
 import org.elasticsearch.xpack.core.security.action.apikey.InvalidateApiKeyRequest;
 import org.elasticsearch.xpack.core.security.action.apikey.InvalidateApiKeyRequest;
 import org.elasticsearch.xpack.core.security.action.apikey.InvalidateApiKeyResponse;
 import org.elasticsearch.xpack.core.security.action.apikey.InvalidateApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction;
+import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequestBuilder;
+import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse;
 import org.elasticsearch.xpack.core.security.action.user.PutUserAction;
 import org.elasticsearch.xpack.core.security.action.user.PutUserAction;
 import org.elasticsearch.xpack.core.security.action.user.PutUserRequest;
 import org.elasticsearch.xpack.core.security.action.user.PutUserRequest;
 import org.elasticsearch.xpack.core.security.action.user.PutUserResponse;
 import org.elasticsearch.xpack.core.security.action.user.PutUserResponse;
@@ -112,6 +115,7 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
         return Settings.builder()
         return Settings.builder()
             .put(super.nodeSettings(nodeOrdinal, otherSettings))
             .put(super.nodeSettings(nodeOrdinal, otherSettings))
             .put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), true)
             .put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), true)
+            .put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true)
             .put(ApiKeyService.DELETE_INTERVAL.getKey(), TimeValue.timeValueMillis(DELETE_INTERVAL_MILLIS))
             .put(ApiKeyService.DELETE_INTERVAL.getKey(), TimeValue.timeValueMillis(DELETE_INTERVAL_MILLIS))
             .put(ApiKeyService.DELETE_TIMEOUT.getKey(), TimeValue.timeValueSeconds(5L))
             .put(ApiKeyService.DELETE_TIMEOUT.getKey(), TimeValue.timeValueSeconds(5L))
             .put("xpack.security.crypto.thread_pool.queue_size", CRYPTO_THREAD_POOL_QUEUE_SIZE)
             .put("xpack.security.crypto.thread_pool.queue_size", CRYPTO_THREAD_POOL_QUEUE_SIZE)
@@ -1109,7 +1113,9 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
             Collections.singletonMap("Authorization", basicAuthHeaderValue(ES_TEST_ROOT_USER, TEST_PASSWORD_SECURE_STRING))
             Collections.singletonMap("Authorization", basicAuthHeaderValue(ES_TEST_ROOT_USER, TEST_PASSWORD_SECURE_STRING))
         );
         );
         final CreateApiKeyResponse response = new CreateApiKeyRequestBuilder(client).setName("key-1")
         final CreateApiKeyResponse response = new CreateApiKeyRequestBuilder(client).setName("key-1")
-            .setRoleDescriptors(Collections.singletonList(new RoleDescriptor("role", new String[] { "manage_api_key" }, null, null)))
+            .setRoleDescriptors(
+                Collections.singletonList(new RoleDescriptor("role", new String[] { "manage_api_key", "manage_token" }, null, null))
+            )
             .setMetadata(ApiKeyTests.randomMetadata())
             .setMetadata(ApiKeyTests.randomMetadata())
             .get();
             .get();
 
 
@@ -1120,7 +1126,17 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase {
         // use the first ApiKey for authorized action
         // use the first ApiKey for authorized action
         final String base64ApiKeyKeyValue = Base64.getEncoder()
         final String base64ApiKeyKeyValue = Base64.getEncoder()
             .encodeToString((response.getId() + ":" + response.getKey().toString()).getBytes(StandardCharsets.UTF_8));
             .encodeToString((response.getId() + ":" + response.getKey().toString()).getBytes(StandardCharsets.UTF_8));
-        final Client clientKey1 = client().filterWithHeader(Collections.singletonMap("Authorization", "ApiKey " + base64ApiKeyKeyValue));
+
+        final Client clientKey1;
+        if (randomBoolean()) {
+            clientKey1 = client().filterWithHeader(Collections.singletonMap("Authorization", "ApiKey " + base64ApiKeyKeyValue));
+        } else {
+            final CreateTokenResponse createTokenResponse = new CreateTokenRequestBuilder(
+                client().filterWithHeader(Collections.singletonMap("Authorization", "ApiKey " + base64ApiKeyKeyValue)),
+                CreateTokenAction.INSTANCE
+            ).setGrantType("client_credentials").get();
+            clientKey1 = client().filterWithHeader(Map.of("Authorization", "Bearer " + createTokenResponse.getTokenString()));
+        }
 
 
         final String expectedMessage = "creating derived api keys requires an explicit role descriptor that is empty";
         final String expectedMessage = "creating derived api keys requires an explicit role descriptor that is empty";
 
 

+ 27 - 1
x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/RunAsIntegTests.java

@@ -157,7 +157,7 @@ public class RunAsIntegTests extends SecurityIntegTestCase {
             createApiKeyResponse.getEntity().getContent()
             createApiKeyResponse.getEntity().getContent()
         );
         );
 
 
-        final boolean runAsTestUser = false;
+        final boolean runAsTestUser = randomBoolean();
 
 
         final Request authenticateRequest = new Request("GET", "/_security/_authenticate");
         final Request authenticateRequest = new Request("GET", "/_security/_authenticate");
         authenticateRequest.setOptions(
         authenticateRequest.setOptions(
@@ -194,6 +194,32 @@ public class RunAsIntegTests extends SecurityIntegTestCase {
             final ResponseException e = expectThrows(ResponseException.class, () -> getRestClient().performRequest(getUserRequest));
             final ResponseException e = expectThrows(ResponseException.class, () -> getRestClient().performRequest(getUserRequest));
             assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(403));
             assertThat(e.getResponse().getStatusLine().getStatusCode(), equalTo(403));
         }
         }
+
+        // Run-as ignored if using a token created by the API key
+        final Request createTokenRequest = new Request("POST", "/_security/oauth2/token");
+        createTokenRequest.setOptions(
+            createTokenRequest.getOptions().toBuilder().addHeader("Authorization", "ApiKey " + apiKeyMapView.get("encoded"))
+        );
+        createTokenRequest.setJsonEntity("{\"grant_type\":\"client_credentials\"}");
+        final Response createTokenResponse = getRestClient().performRequest(createTokenRequest);
+        final XContentTestUtils.JsonMapView createTokenJsonView = XContentTestUtils.createJsonMapView(
+            createTokenResponse.getEntity().getContent()
+        );
+
+        authenticateRequest.setOptions(
+            RequestOptions.DEFAULT.toBuilder()
+                .addHeader("Authorization", "Bearer " + createTokenJsonView.get("access_token"))
+                .addHeader(
+                    AuthenticationServiceField.RUN_AS_USER_HEADER,
+                    runAsTestUser ? SecuritySettingsSource.TEST_USER_NAME : NO_ROLE_USER
+                )
+        );
+        final Response authenticateResponse2 = getRestClient().performRequest(authenticateRequest);
+        final XContentTestUtils.JsonMapView authenticateJsonView2 = XContentTestUtils.createJsonMapView(
+            authenticateResponse2.getEntity().getContent()
+        );
+        // run-as header is ignored, the user is still the run_as_user
+        assertThat(authenticateJsonView2.get("username"), equalTo(RUN_AS_USER));
     }
     }
 
 
     public void testRunAsIgnoredForOAuthToken() throws IOException {
     public void testRunAsIgnoredForOAuthToken() throws IOException {

+ 53 - 0
x-pack/plugin/security/src/internalClusterTest/java/org/elasticsearch/xpack/security/authc/apikey/ApiKeySingleNodeTests.java

@@ -15,6 +15,7 @@ import org.elasticsearch.action.get.GetRequest;
 import org.elasticsearch.action.get.GetResponse;
 import org.elasticsearch.action.get.GetResponse;
 import org.elasticsearch.action.main.MainAction;
 import org.elasticsearch.action.main.MainAction;
 import org.elasticsearch.action.main.MainRequest;
 import org.elasticsearch.action.main.MainRequest;
+import org.elasticsearch.client.internal.Client;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.common.settings.SecureString;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.Settings;
@@ -27,6 +28,9 @@ import org.elasticsearch.xpack.core.XPackSettings;
 import org.elasticsearch.xpack.core.security.action.apikey.CreateApiKeyAction;
 import org.elasticsearch.xpack.core.security.action.apikey.CreateApiKeyAction;
 import org.elasticsearch.xpack.core.security.action.apikey.CreateApiKeyRequest;
 import org.elasticsearch.xpack.core.security.action.apikey.CreateApiKeyRequest;
 import org.elasticsearch.xpack.core.security.action.apikey.CreateApiKeyResponse;
 import org.elasticsearch.xpack.core.security.action.apikey.CreateApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.apikey.GetApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.apikey.GetApiKeyRequest;
+import org.elasticsearch.xpack.core.security.action.apikey.GetApiKeyResponse;
 import org.elasticsearch.xpack.core.security.action.apikey.GrantApiKeyAction;
 import org.elasticsearch.xpack.core.security.action.apikey.GrantApiKeyAction;
 import org.elasticsearch.xpack.core.security.action.apikey.GrantApiKeyRequest;
 import org.elasticsearch.xpack.core.security.action.apikey.GrantApiKeyRequest;
 import org.elasticsearch.xpack.core.security.action.apikey.QueryApiKeyAction;
 import org.elasticsearch.xpack.core.security.action.apikey.QueryApiKeyAction;
@@ -35,6 +39,9 @@ import org.elasticsearch.xpack.core.security.action.apikey.QueryApiKeyResponse;
 import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenAction;
 import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenAction;
 import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenRequest;
 import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenRequest;
 import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenResponse;
 import org.elasticsearch.xpack.core.security.action.service.CreateServiceAccountTokenResponse;
+import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction;
+import org.elasticsearch.xpack.core.security.action.token.CreateTokenRequestBuilder;
+import org.elasticsearch.xpack.core.security.action.token.CreateTokenResponse;
 import org.elasticsearch.xpack.core.security.action.user.PutUserAction;
 import org.elasticsearch.xpack.core.security.action.user.PutUserAction;
 import org.elasticsearch.xpack.core.security.action.user.PutUserRequest;
 import org.elasticsearch.xpack.core.security.action.user.PutUserRequest;
 import org.elasticsearch.xpack.core.security.authc.support.Hasher;
 import org.elasticsearch.xpack.core.security.authc.support.Hasher;
@@ -45,6 +52,7 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.charset.StandardCharsets;
 import java.time.Instant;
 import java.time.Instant;
 import java.util.Base64;
 import java.util.Base64;
+import java.util.Collections;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 
 
@@ -60,6 +68,7 @@ public class ApiKeySingleNodeTests extends SecuritySingleNodeTestCase {
     protected Settings nodeSettings() {
     protected Settings nodeSettings() {
         Settings.Builder builder = Settings.builder().put(super.nodeSettings());
         Settings.Builder builder = Settings.builder().put(super.nodeSettings());
         builder.put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), true);
         builder.put(XPackSettings.API_KEY_SERVICE_ENABLED_SETTING.getKey(), true);
+        builder.put(XPackSettings.TOKEN_SERVICE_ENABLED_SETTING.getKey(), true);
         return builder.build();
         return builder.build();
     }
     }
 
 
@@ -185,6 +194,50 @@ public class ApiKeySingleNodeTests extends SecuritySingleNodeTestCase {
         assertThat(roleDescriptor, equalTo(ServiceAccountService.getServiceAccounts().get("elastic/fleet-server").roleDescriptor()));
         assertThat(roleDescriptor, equalTo(ServiceAccountService.getServiceAccounts().get("elastic/fleet-server").roleDescriptor()));
     }
     }
 
 
+    public void testGetApiKeyWorksForTheApiKeyItself() {
+        final String apiKeyName = randomAlphaOfLength(10);
+        final CreateApiKeyResponse createApiKeyResponse = client().execute(
+            CreateApiKeyAction.INSTANCE,
+            new CreateApiKeyRequest(
+                apiKeyName,
+                List.of(new RoleDescriptor("x", new String[] { "manage_own_api_key", "manage_token" }, null, null, null, null, null, null)),
+                null,
+                null
+            )
+        ).actionGet();
+
+        final String apiKeyId = createApiKeyResponse.getId();
+        final String base64ApiKeyKeyValue = Base64.getEncoder()
+            .encodeToString((apiKeyId + ":" + createApiKeyResponse.getKey().toString()).getBytes(StandardCharsets.UTF_8));
+
+        // Works for both the API key itself or the token created by it
+        final Client clientKey1;
+        if (randomBoolean()) {
+            clientKey1 = client().filterWithHeader(Collections.singletonMap("Authorization", "ApiKey " + base64ApiKeyKeyValue));
+        } else {
+            final CreateTokenResponse createTokenResponse = new CreateTokenRequestBuilder(
+                client().filterWithHeader(Collections.singletonMap("Authorization", "ApiKey " + base64ApiKeyKeyValue)),
+                CreateTokenAction.INSTANCE
+            ).setGrantType("client_credentials").get();
+            clientKey1 = client().filterWithHeader(Map.of("Authorization", "Bearer " + createTokenResponse.getTokenString()));
+        }
+
+        // Can get its own info
+        final GetApiKeyResponse getApiKeyResponse = clientKey1.execute(
+            GetApiKeyAction.INSTANCE,
+            GetApiKeyRequest.usingApiKeyId(apiKeyId, randomBoolean())
+        ).actionGet();
+        assertThat(getApiKeyResponse.getApiKeyInfos().length, equalTo(1));
+        assertThat(getApiKeyResponse.getApiKeyInfos()[0].getId(), equalTo(apiKeyId));
+
+        // Cannot get any other keys
+        final ElasticsearchSecurityException e = expectThrows(
+            ElasticsearchSecurityException.class,
+            () -> clientKey1.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.forAllApiKeys()).actionGet()
+        );
+        assertThat(e.getMessage(), containsString("unauthorized for API key id [" + apiKeyId + "]"));
+    }
+
     private Map<String, Object> getApiKeyDocument(String apiKeyId) {
     private Map<String, Object> getApiKeyDocument(String apiKeyId) {
         final GetResponse getResponse = client().execute(GetAction.INSTANCE, new GetRequest(".security-7", apiKeyId)).actionGet();
         final GetResponse getResponse = client().execute(GetAction.INSTANCE, new GetRequest(".security-7", apiKeyId)).actionGet();
         return getResponse.getSource();
         return getResponse.getSource();

+ 5 - 3
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java

@@ -1350,10 +1350,12 @@ public class ApiKeyService {
      * @return A map for the metadata or an empty map if no metadata is found.
      * @return A map for the metadata or an empty map if no metadata is found.
      */
      */
     public static Map<String, Object> getApiKeyMetadata(Authentication authentication) {
     public static Map<String, Object> getApiKeyMetadata(Authentication authentication) {
-        if (false == authentication.isAuthenticatedWithApiKey()) {
+        if (false == authentication.isAuthenticatedAsApiKey()) {
             throw new IllegalArgumentException(
             throw new IllegalArgumentException(
-                "authentication type must be [api_key], got ["
-                    + authentication.getAuthenticationType().name().toLowerCase(Locale.ROOT)
+                "authentication realm must be ["
+                    + AuthenticationField.API_KEY_REALM_TYPE
+                    + "], got ["
+                    + authentication.getAuthenticatedBy().getType()
                     + "]"
                     + "]"
             );
             );
         }
         }

+ 1 - 1
x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java

@@ -879,7 +879,7 @@ public class AuthorizationService {
             userText = userText + " run as [" + authentication.getUser().principal() + "]";
             userText = userText + " run as [" + authentication.getUser().principal() + "]";
         }
         }
         // check for authentication by API key
         // check for authentication by API key
-        if (authentication.isAuthenticatedWithApiKey()) {
+        if (authentication.isAuthenticatedAsApiKey()) {
             final String apiKeyId = (String) authentication.getMetadata().get(AuthenticationField.API_KEY_ID_KEY);
             final String apiKeyId = (String) authentication.getMetadata().get(AuthenticationField.API_KEY_ID_KEY);
             assert apiKeyId != null : "api key id must be present in the metadata";
             assert apiKeyId != null : "api key id must be present in the metadata";
             userText = "API key id [" + apiKeyId + "] of " + userText;
             userText = "API key id [" + apiKeyId + "] of " + userText;

+ 19 - 13
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java

@@ -1230,21 +1230,24 @@ public class ApiKeyServiceTests extends ESTestCase {
         assertThat(ApiKeyService.getCreatorRealmName(authentication2), equalTo(lookupRealmRef.getName()));
         assertThat(ApiKeyService.getCreatorRealmName(authentication2), equalTo(lookupRealmRef.getName()));
         assertThat(ApiKeyService.getCreatorRealmType(authentication2), equalTo(lookupRealmRef.getType()));
         assertThat(ApiKeyService.getCreatorRealmType(authentication2), equalTo(lookupRealmRef.getType()));
 
 
-        // Non API Key
-        final Authentication authentication3 = randomFrom(
-            AuthenticationTests.randomRealmAuthentication(false),
-            AuthenticationTests.randomServiceAccountAuthentication(),
-            AuthenticationTests.randomAnonymousAuthentication(),
-            AuthenticationTests.randomInternalAuthentication()
-        );
-
+        // Realm
+        final Authentication authentication3 = AuthenticationTests.randomRealmAuthentication(false);
         assertThat(ApiKeyService.getCreatorRealmName(authentication3), equalTo(authentication3.getSourceRealm().getName()));
         assertThat(ApiKeyService.getCreatorRealmName(authentication3), equalTo(authentication3.getSourceRealm().getName()));
         assertThat(ApiKeyService.getCreatorRealmType(authentication3), equalTo(authentication3.getSourceRealm().getType()));
         assertThat(ApiKeyService.getCreatorRealmType(authentication3), equalTo(authentication3.getSourceRealm().getType()));
 
 
-        // Non API Key run-as
+        // Realm run-as
         final Authentication authentication4 = authentication3.runAs(AuthenticationTests.randomUser(), lookupRealmRef);
         final Authentication authentication4 = authentication3.runAs(AuthenticationTests.randomUser(), lookupRealmRef);
         assertThat(ApiKeyService.getCreatorRealmName(authentication4), equalTo(lookupRealmRef.getName()));
         assertThat(ApiKeyService.getCreatorRealmName(authentication4), equalTo(lookupRealmRef.getName()));
         assertThat(ApiKeyService.getCreatorRealmType(authentication4), equalTo(lookupRealmRef.getType()));
         assertThat(ApiKeyService.getCreatorRealmType(authentication4), equalTo(lookupRealmRef.getType()));
+
+        // Others (cannot run-as)
+        final Authentication authentication5 = randomFrom(
+            AuthenticationTests.randomServiceAccountAuthentication(),
+            AuthenticationTests.randomAnonymousAuthentication(),
+            AuthenticationTests.randomInternalAuthentication()
+        );
+        assertThat(ApiKeyService.getCreatorRealmName(authentication5), equalTo(authentication5.getSourceRealm().getName()));
+        assertThat(ApiKeyService.getCreatorRealmType(authentication5), equalTo(authentication5.getSourceRealm().getType()));
     }
     }
 
 
     public void testAuthWillTerminateIfGetThreadPoolIsSaturated() throws ExecutionException, InterruptedException {
     public void testAuthWillTerminateIfGetThreadPoolIsSaturated() throws ExecutionException, InterruptedException {
@@ -1453,7 +1456,10 @@ public class ApiKeyServiceTests extends ESTestCase {
     public void testGetApiKeyMetadata() throws IOException {
     public void testGetApiKeyMetadata() throws IOException {
         final Authentication apiKeyAuthentication = mock(Authentication.class);
         final Authentication apiKeyAuthentication = mock(Authentication.class);
         when(apiKeyAuthentication.getAuthenticationType()).thenReturn(AuthenticationType.API_KEY);
         when(apiKeyAuthentication.getAuthenticationType()).thenReturn(AuthenticationType.API_KEY);
-        when(apiKeyAuthentication.isAuthenticatedWithApiKey()).thenCallRealMethod();
+        when(apiKeyAuthentication.getAuthenticatedBy()).thenReturn(
+            new RealmRef(AuthenticationField.API_KEY_REALM_NAME, AuthenticationField.API_KEY_REALM_TYPE, randomAlphaOfLengthBetween(3, 8))
+        );
+        when(apiKeyAuthentication.isAuthenticatedAsApiKey()).thenCallRealMethod();
         final Map<String, Object> apiKeyMetadata = ApiKeyTests.randomMetadata();
         final Map<String, Object> apiKeyMetadata = ApiKeyTests.randomMetadata();
         if (apiKeyMetadata == null) {
         if (apiKeyMetadata == null) {
             when(apiKeyAuthentication.getMetadata()).thenReturn(Map.of());
             when(apiKeyAuthentication.getMetadata()).thenReturn(Map.of());
@@ -1470,14 +1476,14 @@ public class ApiKeyServiceTests extends ESTestCase {
         }
         }
 
 
         final Authentication authentication = mock(Authentication.class);
         final Authentication authentication = mock(Authentication.class);
-        when(authentication.getAuthenticationType()).thenReturn(
-            randomValueOtherThan(AuthenticationType.API_KEY, () -> randomFrom(AuthenticationType.values()))
+        when(authentication.getAuthenticatedBy()).thenReturn(
+            new RealmRef(randomAlphaOfLengthBetween(5, 10), randomAlphaOfLengthBetween(5, 10), randomAlphaOfLengthBetween(3, 8))
         );
         );
         final IllegalArgumentException e = expectThrows(
         final IllegalArgumentException e = expectThrows(
             IllegalArgumentException.class,
             IllegalArgumentException.class,
             () -> ApiKeyService.getApiKeyMetadata(authentication)
             () -> ApiKeyService.getApiKeyMetadata(authentication)
         );
         );
-        assertThat(e.getMessage(), containsString("authentication type must be [api_key]"));
+        assertThat(e.getMessage(), containsString("authentication realm must be [_es_api_key]"));
     }
     }
 
 
     public static class Utils {
     public static class Utils {

+ 13 - 3
x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java

@@ -336,11 +336,14 @@ public class RBACEngineTests extends ESTestCase {
         final TransportRequest request = GetApiKeyRequest.usingApiKeyId(apiKeyId, false);
         final TransportRequest request = GetApiKeyRequest.usingApiKeyId(apiKeyId, false);
         final Authentication authentication = mock(Authentication.class);
         final Authentication authentication = mock(Authentication.class);
         final Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class);
         final Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class);
+        when(authenticatedBy.getName()).thenReturn(AuthenticationField.API_KEY_REALM_NAME);
+        when(authenticatedBy.getType()).thenReturn(AuthenticationField.API_KEY_REALM_TYPE);
         when(authentication.getUser()).thenReturn(user);
         when(authentication.getUser()).thenReturn(user);
         when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
         when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
         when(authentication.getAuthenticationType()).thenReturn(AuthenticationType.API_KEY);
         when(authentication.getAuthenticationType()).thenReturn(AuthenticationType.API_KEY);
+        when(authentication.getSourceRealm()).thenReturn(authenticatedBy);
         when(authentication.getMetadata()).thenReturn(Map.of(AuthenticationField.API_KEY_ID_KEY, apiKeyId));
         when(authentication.getMetadata()).thenReturn(Map.of(AuthenticationField.API_KEY_ID_KEY, apiKeyId));
-        when(authentication.isAuthenticatedWithApiKey()).thenCallRealMethod();
+        when(authentication.isAuthenticatedAsApiKey()).thenCallRealMethod();
         when(authentication.isApiKey()).thenCallRealMethod();
         when(authentication.isApiKey()).thenCallRealMethod();
 
 
         assertTrue(engine.checkSameUserPermissions(GetApiKeyAction.NAME, request, authentication));
         assertTrue(engine.checkSameUserPermissions(GetApiKeyAction.NAME, request, authentication));
@@ -354,9 +357,11 @@ public class RBACEngineTests extends ESTestCase {
         final Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class);
         final Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class);
         when(authentication.getUser()).thenReturn(user);
         when(authentication.getUser()).thenReturn(user);
         when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
         when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
+        when(authenticatedBy.getName()).thenReturn(AuthenticationField.API_KEY_REALM_NAME);
         when(authenticatedBy.getType()).thenReturn(AuthenticationField.API_KEY_REALM_TYPE);
         when(authenticatedBy.getType()).thenReturn(AuthenticationField.API_KEY_REALM_TYPE);
+        when(authentication.getSourceRealm()).thenReturn(authenticatedBy);
         when(authentication.getMetadata()).thenReturn(Map.of(AuthenticationField.API_KEY_ID_KEY, randomAlphaOfLengthBetween(4, 7)));
         when(authentication.getMetadata()).thenReturn(Map.of(AuthenticationField.API_KEY_ID_KEY, randomAlphaOfLengthBetween(4, 7)));
-        when(authentication.isAuthenticatedWithApiKey()).thenCallRealMethod();
+        when(authentication.isAuthenticatedAsApiKey()).thenCallRealMethod();
         when(authentication.isApiKey()).thenCallRealMethod();
         when(authentication.isApiKey()).thenCallRealMethod();
 
 
         assertFalse(engine.checkSameUserPermissions(GetApiKeyAction.NAME, request, authentication));
         assertFalse(engine.checkSameUserPermissions(GetApiKeyAction.NAME, request, authentication));
@@ -371,10 +376,15 @@ public class RBACEngineTests extends ESTestCase {
         final Authentication.RealmRef lookedupBy = mock(Authentication.RealmRef.class);
         final Authentication.RealmRef lookedupBy = mock(Authentication.RealmRef.class);
         when(authentication.getUser()).thenReturn(user);
         when(authentication.getUser()).thenReturn(user);
         when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
         when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
+        when(authenticatedBy.getName()).thenReturn(AuthenticationField.API_KEY_REALM_NAME);
+        when(authenticatedBy.getType()).thenReturn(AuthenticationField.API_KEY_REALM_TYPE);
         when(authentication.getLookedUpBy()).thenReturn(lookedupBy);
         when(authentication.getLookedUpBy()).thenReturn(lookedupBy);
+        when(lookedupBy.getName()).thenReturn("name");
+        when(lookedupBy.getType()).thenReturn("type");
+        when(authentication.getSourceRealm()).thenReturn(lookedupBy);
         when(authentication.getAuthenticationType()).thenReturn(AuthenticationType.API_KEY);
         when(authentication.getAuthenticationType()).thenReturn(AuthenticationType.API_KEY);
         when(authentication.getMetadata()).thenReturn(Map.of(AuthenticationField.API_KEY_ID_KEY, randomAlphaOfLengthBetween(4, 7)));
         when(authentication.getMetadata()).thenReturn(Map.of(AuthenticationField.API_KEY_ID_KEY, randomAlphaOfLengthBetween(4, 7)));
-        when(authentication.isAuthenticatedWithApiKey()).thenCallRealMethod();
+        when(authentication.isAuthenticatedAsApiKey()).thenCallRealMethod();
         when(authentication.isApiKey()).thenCallRealMethod();
         when(authentication.isApiKey()).thenCallRealMethod();
 
 
         assertFalse(engine.checkSameUserPermissions(GetApiKeyAction.NAME, request, authentication));
         assertFalse(engine.checkSameUserPermissions(GetApiKeyAction.NAME, request, authentication));