diff --git a/x-pack/docs/en/rest-api/security/get-api-keys.asciidoc b/x-pack/docs/en/rest-api/security/get-api-keys.asciidoc index 632f3f3ea93..b673fe94c38 100644 --- a/x-pack/docs/en/rest-api/security/get-api-keys.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-api-keys.asciidoc @@ -30,12 +30,12 @@ The following parameters can be specified in the query parameters of a GET reque pertain to retrieving api keys: `id`:: -(Optional, string) An API key id. This parameter cannot be used with any of +(Optional, string) An API key id. This parameter cannot be used with any of `name`, `realm_name` or `username` are used. `name`:: (Optional, string) An API key name. This parameter cannot be used with any of -`id`, `realm_name` or `username` are used. +`id`, `realm_name` or `username` are used. It supports prefix search with wildcard. `realm_name`:: (Optional, string) The name of an authentication realm. This parameter cannot be @@ -47,7 +47,7 @@ either `id` or `name` or when `owner` flag is set to `true`. `owner`:: (Optional, boolean) A boolean flag that can be used to query API keys owned -by the currently authenticated user. Defaults to false. +by the currently authenticated user. Defaults to false. The 'realm_name' or 'username' parameters cannot be specified when this parameter is set to 'true' as they are assumed to be the currently authenticated ones. @@ -101,6 +101,14 @@ GET /_security/api_key?name=my-api-key -------------------------------------------------- // TEST[continued] +API key name supports prefix search by using wildcard: + +[source,console] +-------------------------------------------------- +GET /_security/api_key?name=my-* +-------------------------------------------------- +// TEST[continued] + The following example retrieves all API keys for the `native1` realm: [source,console] diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java index b96ed7312e7..821205f3897 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/ApiKeyService.java @@ -830,8 +830,12 @@ public class ApiKeyService { if (Strings.hasText(userName)) { boolQuery.filter(QueryBuilders.termQuery("creator.principal", userName)); } - if (Strings.hasText(apiKeyName)) { - boolQuery.filter(QueryBuilders.termQuery("name", apiKeyName)); + if (Strings.hasText(apiKeyName) && "*".equals(apiKeyName) == false) { + if (apiKeyName.endsWith("*")) { + boolQuery.filter(QueryBuilders.prefixQuery("name", apiKeyName.substring(0, apiKeyName.length() - 1))); + } else { + boolQuery.filter(QueryBuilders.termQuery("name", apiKeyName)); + } } if (Strings.hasText(apiKeyId)) { boolQuery.filter(QueryBuilders.termQuery("_id", apiKeyId)); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java index 0652da94ebd..ef3c4914353 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyIntegTests.java @@ -546,14 +546,43 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase { } public void testGetApiKeysForApiKeyName() throws InterruptedException, ExecutionException { - List responses = createApiKeys(1, null); - Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken - .basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + final Map headers = Collections.singletonMap( + "Authorization", + UsernamePasswordToken.basicAuthHeaderValue( + SecuritySettingsSource.TEST_SUPERUSER, + SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)); + + final int noOfApiKeys = randomIntBetween(1, 3); + final List createApiKeyResponses1 = createApiKeys(noOfApiKeys, null); + final List createApiKeyResponses2 = createApiKeys( + headers, noOfApiKeys, "another-test-key-", null, "monitor"); + + Client client = client().filterWithHeader(headers); SecurityClient securityClient = new SecurityClient(client); PlainActionFuture listener = new PlainActionFuture<>(); + List responses = randomFrom(createApiKeyResponses1, createApiKeyResponses2); securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName(responses.get(0).getName(), false), listener); - GetApiKeyResponse response = listener.get(); - verifyGetResponse(1, responses, response, Collections.singleton(responses.get(0).getId()), null); + verifyGetResponse(1, responses, listener.get(), Collections.singleton(responses.get(0).getId()), null); + + PlainActionFuture listener2 = new PlainActionFuture<>(); + securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName("test-key*", false), listener2); + verifyGetResponse(noOfApiKeys, createApiKeyResponses1, listener2.get(), + createApiKeyResponses1.stream().map(CreateApiKeyResponse::getId).collect(Collectors.toSet()), null); + + PlainActionFuture listener3 = new PlainActionFuture<>(); + securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName("*", false), listener3); + responses = Stream.concat(createApiKeyResponses1.stream(), createApiKeyResponses2.stream()).collect(Collectors.toList()); + verifyGetResponse(2 * noOfApiKeys, responses, listener3.get(), + responses.stream().map(CreateApiKeyResponse::getId).collect(Collectors.toSet()), null); + + PlainActionFuture listener4 = new PlainActionFuture<>(); + securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName("does-not-exist*", false), listener4); + verifyGetResponse(0, Collections.emptyList(), listener4.get(), Collections.emptySet(), null); + + PlainActionFuture listener5 = new PlainActionFuture<>(); + securityClient.getApiKey(GetApiKeyRequest.usingApiKeyName("another-test-key*", false), listener5); + verifyGetResponse(noOfApiKeys, createApiKeyResponses2, listener5.get(), + createApiKeyResponses2.stream().map(CreateApiKeyResponse::getId).collect(Collectors.toSet()), null); } public void testGetApiKeysOwnedByCurrentAuthenticatedUser() throws InterruptedException, ExecutionException { @@ -994,13 +1023,18 @@ public class ApiKeyIntegTests extends SecurityIntegTestCase { private List createApiKeys(Map headers, int noOfApiKeys, TimeValue expiration, String... clusterPrivileges) { + return createApiKeys(headers, noOfApiKeys, "test-key-", expiration, clusterPrivileges); + } + + private List createApiKeys(Map headers, + int noOfApiKeys, String namePrefix, TimeValue expiration, String... clusterPrivileges) { List responses = new ArrayList<>(); for (int i = 0; i < noOfApiKeys; i++) { final RoleDescriptor descriptor = new RoleDescriptor("role", clusterPrivileges, null, null); Client client = client().filterWithHeader(headers); SecurityClient securityClient = new SecurityClient(client); final CreateApiKeyResponse response = securityClient.prepareCreateApiKey() - .setName("test-key-" + randomAlphaOfLengthBetween(5, 9) + i).setExpiration(expiration) + .setName(namePrefix + randomAlphaOfLengthBetween(5, 9) + i).setExpiration(expiration) .setRoleDescriptors(Collections.singletonList(descriptor)).get(); assertNotNull(response.getId()); assertNotNull(response.getKey());