diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
index fefb5771dc801..67793d4225261 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java
@@ -43,6 +43,7 @@
import org.elasticsearch.client.security.EnableUserRequest;
import org.elasticsearch.client.security.GetApiKeyRequest;
import org.elasticsearch.client.security.GetApiKeyResponse;
+import org.elasticsearch.client.security.GetMyApiKeyRequest;
import org.elasticsearch.client.security.GetPrivilegesRequest;
import org.elasticsearch.client.security.GetPrivilegesResponse;
import org.elasticsearch.client.security.GetRoleMappingsRequest;
@@ -59,6 +60,7 @@
import org.elasticsearch.client.security.HasPrivilegesResponse;
import org.elasticsearch.client.security.InvalidateApiKeyRequest;
import org.elasticsearch.client.security.InvalidateApiKeyResponse;
+import org.elasticsearch.client.security.InvalidateMyApiKeyRequest;
import org.elasticsearch.client.security.InvalidateTokenRequest;
import org.elasticsearch.client.security.InvalidateTokenResponse;
import org.elasticsearch.client.security.PutPrivilegesRequest;
@@ -909,6 +911,36 @@ public void getApiKeyAsync(final GetApiKeyRequest request, final RequestOptions
GetApiKeyResponse::fromXContent, listener, emptySet());
}
+ /**
+ * Retrieve information for API key(s) owned by authenticated user.
+ * See
+ * the docs for more.
+ *
+ * @param request the request to retrieve API key(s)
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @return the response from the create API key call
+ * @throws IOException in case there is a problem sending the request or parsing back the response
+ */
+ public GetApiKeyResponse getMyApiKey(final GetMyApiKeyRequest request, final RequestOptions options) throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::getMyApiKey, options,
+ GetApiKeyResponse::fromXContent, emptySet());
+ }
+
+ /**
+ * Asynchronously retrieve information for API key(s) owned by authenticated user.
+ * See
+ * the docs for more.
+ *
+ * @param request the request to retrieve API key(s)
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @param listener the listener to be notified upon request completion
+ */
+ public void getMyApiKeyAsync(final GetMyApiKeyRequest request, final RequestOptions options,
+ final ActionListener listener) {
+ restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::getMyApiKey, options,
+ GetApiKeyResponse::fromXContent, listener, emptySet());
+ }
+
/**
* Invalidate API Key(s).
* See
@@ -939,4 +971,35 @@ public void invalidateApiKeyAsync(final InvalidateApiKeyRequest request, final R
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::invalidateApiKey, options,
InvalidateApiKeyResponse::fromXContent, listener, emptySet());
}
+
+ /**
+ * Invalidate API key(s) owned by authenticated user.
+ * See
+ * the docs for more.
+ *
+ * @param request the request to invalidate API key(s)
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @return the response from the invalidate API key call
+ * @throws IOException in case there is a problem sending the request or parsing back the response
+ */
+ public InvalidateApiKeyResponse invalidateMyApiKey(final InvalidateMyApiKeyRequest request, final RequestOptions options)
+ throws IOException {
+ return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::invalidateMyApiKey, options,
+ InvalidateApiKeyResponse::fromXContent, emptySet());
+ }
+
+ /**
+ * Asynchronously invalidates API key(s) owned by authenticated user.
+ * See
+ * the docs for more.
+ *
+ * @param request the request to invalidate API key(s)
+ * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+ * @param listener the listener to be notified upon request completion
+ */
+ public void invalidateMyApiKeyAsync(final InvalidateMyApiKeyRequest request, final RequestOptions options,
+ final ActionListener listener) {
+ restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::invalidateMyApiKey, options,
+ InvalidateApiKeyResponse::fromXContent, listener, emptySet());
+ }
}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java
index f686167e211bb..5c8790af65f7e 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java
@@ -35,12 +35,14 @@
import org.elasticsearch.client.security.DisableUserRequest;
import org.elasticsearch.client.security.EnableUserRequest;
import org.elasticsearch.client.security.GetApiKeyRequest;
+import org.elasticsearch.client.security.GetMyApiKeyRequest;
import org.elasticsearch.client.security.GetPrivilegesRequest;
import org.elasticsearch.client.security.GetRoleMappingsRequest;
import org.elasticsearch.client.security.GetRolesRequest;
import org.elasticsearch.client.security.GetUsersRequest;
import org.elasticsearch.client.security.HasPrivilegesRequest;
import org.elasticsearch.client.security.InvalidateApiKeyRequest;
+import org.elasticsearch.client.security.InvalidateMyApiKeyRequest;
import org.elasticsearch.client.security.InvalidateTokenRequest;
import org.elasticsearch.client.security.PutPrivilegesRequest;
import org.elasticsearch.client.security.PutRoleMappingRequest;
@@ -285,10 +287,26 @@ static Request getApiKey(final GetApiKeyRequest getApiKeyRequest) throws IOExcep
return request;
}
+ static Request getMyApiKey(final GetMyApiKeyRequest getMyApiKeyRequest) throws IOException {
+ final Request request = new Request(HttpGet.METHOD_NAME, "/_security/api_key/my");
+ if (Strings.hasText(getMyApiKeyRequest.getId())) {
+ request.addParameter("id", getMyApiKeyRequest.getId());
+ }
+ if (Strings.hasText(getMyApiKeyRequest.getName())) {
+ request.addParameter("name", getMyApiKeyRequest.getName());
+ }
+ return request;
+ }
+
static Request invalidateApiKey(final InvalidateApiKeyRequest invalidateApiKeyRequest) throws IOException {
final Request request = new Request(HttpDelete.METHOD_NAME, "/_security/api_key");
request.setEntity(createEntity(invalidateApiKeyRequest, REQUEST_BODY_CONTENT_TYPE));
- final RequestConverters.Params params = new RequestConverters.Params(request);
+ return request;
+ }
+
+ static Request invalidateMyApiKey(final InvalidateMyApiKeyRequest invalidateMyApiKeyRequest) throws IOException {
+ final Request request = new Request(HttpDelete.METHOD_NAME, "/_security/api_key/my");
+ request.setEntity(createEntity(invalidateMyApiKeyRequest, REQUEST_BODY_CONTENT_TYPE));
return request;
}
}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetMyApiKeyRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetMyApiKeyRequest.java
new file mode 100644
index 0000000000000..800b01262d48f
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetMyApiKeyRequest.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.Validatable;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+/**
+ * Request for retrieving information for API key(s) owned by the authenticated user.
+ */
+public final class GetMyApiKeyRequest implements Validatable, ToXContentObject {
+
+ private final String id;
+ private final String name;
+
+ public GetMyApiKeyRequest(@Nullable String apiKeyId, @Nullable String apiKeyName) {
+ this.id = apiKeyId;
+ this.name = apiKeyName;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Creates request for given api key id
+ * @param apiKeyId api key id
+ * @return {@link GetMyApiKeyRequest}
+ */
+ public static GetMyApiKeyRequest usingApiKeyId(String apiKeyId) {
+ return new GetMyApiKeyRequest(apiKeyId, null);
+ }
+
+ /**
+ * Creates request for given api key name
+ * @param apiKeyName api key name
+ * @return {@link GetMyApiKeyRequest}
+ */
+ public static GetMyApiKeyRequest usingApiKeyName(String apiKeyName) {
+ return new GetMyApiKeyRequest(null, apiKeyName);
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ return builder;
+ }
+
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateMyApiKeyRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateMyApiKeyRequest.java
new file mode 100644
index 0000000000000..2f276caceb297
--- /dev/null
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateMyApiKeyRequest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.elasticsearch.client.security;
+
+import org.elasticsearch.client.Validatable;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+/**
+ * Request for invalidating API key(s) for the authenticated user so that it can no longer be used.
+ */
+public final class InvalidateMyApiKeyRequest implements Validatable, ToXContentObject {
+
+ private final String id;
+ private final String name;
+
+ public InvalidateMyApiKeyRequest(@Nullable String apiKeyId, @Nullable String apiKeyName) {
+ this.id = apiKeyId;
+ this.name = apiKeyName;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Creates invalidate API key request for given api key id
+ * @param apiKeyId api key id
+ * @return {@link InvalidateMyApiKeyRequest}
+ */
+ public static InvalidateMyApiKeyRequest usingApiKeyId(String apiKeyId) {
+ return new InvalidateMyApiKeyRequest(apiKeyId, null);
+ }
+
+ /**
+ * Creates invalidate API key request for given api key name
+ * @param apiKeyName api key name
+ * @return {@link InvalidateMyApiKeyRequest}
+ */
+ public static InvalidateMyApiKeyRequest usingApiKeyName(String apiKeyName) {
+ return new InvalidateMyApiKeyRequest(null, apiKeyName);
+ }
+
+ @Override
+ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+ builder.startObject();
+ if (id != null) {
+ builder.field("id", id);
+ }
+ if (name != null) {
+ builder.field("name", name);
+ }
+ return builder.endObject();
+ }
+}
diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java
index c6dc6910d97b0..cb5778954eaff 100644
--- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java
+++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java
@@ -311,6 +311,9 @@ public static class ClusterPrivilegeName {
public static final String TRANSPORT_CLIENT = "transport_client";
public static final String MANAGE_SECURITY = "manage_security";
public static final String MANAGE_SAML = "manage_saml";
+ public static final String MANAGE_API_KEY = "manage_api_key";
+ public static final String OWNER_MANAGE_API_KEY = "owner_manage_api_key";
+ public static final String CREATE_API_KEY = "create_api_key";
public static final String MANAGE_TOKEN = "manage_token";
public static final String MANAGE_PIPELINE = "manage_pipeline";
public static final String MANAGE_CCR = "manage_ccr";
@@ -319,7 +322,8 @@ public static class ClusterPrivilegeName {
public static final String READ_ILM = "read_ilm";
public static final String[] ALL_ARRAY = new String[] { NONE, ALL, MONITOR, MONITOR_ML, MONITOR_WATCHER, MONITOR_ROLLUP, MANAGE,
MANAGE_ML, MANAGE_WATCHER, MANAGE_ROLLUP, MANAGE_INDEX_TEMPLATES, MANAGE_INGEST_PIPELINES, TRANSPORT_CLIENT,
- MANAGE_SECURITY, MANAGE_SAML, MANAGE_TOKEN, MANAGE_PIPELINE, MANAGE_CCR, READ_CCR, MANAGE_ILM, READ_ILM };
+ MANAGE_SECURITY, MANAGE_SAML, MANAGE_API_KEY, OWNER_MANAGE_API_KEY, CREATE_API_KEY, MANAGE_TOKEN, MANAGE_PIPELINE,
+ MANAGE_CCR, READ_CCR, MANAGE_ILM, READ_ILM };
}
/**
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java
index 99350fc29db8a..5a66024fa7ce2 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java
@@ -33,11 +33,13 @@
import org.elasticsearch.client.security.DisableUserRequest;
import org.elasticsearch.client.security.EnableUserRequest;
import org.elasticsearch.client.security.GetApiKeyRequest;
+import org.elasticsearch.client.security.GetMyApiKeyRequest;
import org.elasticsearch.client.security.GetPrivilegesRequest;
import org.elasticsearch.client.security.GetRoleMappingsRequest;
import org.elasticsearch.client.security.GetRolesRequest;
import org.elasticsearch.client.security.GetUsersRequest;
import org.elasticsearch.client.security.InvalidateApiKeyRequest;
+import org.elasticsearch.client.security.InvalidateMyApiKeyRequest;
import org.elasticsearch.client.security.PutPrivilegesRequest;
import org.elasticsearch.client.security.PutRoleMappingRequest;
import org.elasticsearch.client.security.PutRoleRequest;
@@ -461,4 +463,24 @@ public void testInvalidateApiKey() throws IOException {
assertEquals("/_security/api_key", request.getEndpoint());
assertToXContentBody(invalidateApiKeyRequest, request.getEntity());
}
+
+ public void testGetMyApiKey() throws IOException {
+ String apiKeyId = randomAlphaOfLength(5);
+ final GetMyApiKeyRequest getApiKeyRequest = GetMyApiKeyRequest.usingApiKeyId(apiKeyId);
+ final Request request = SecurityRequestConverters.getMyApiKey(getApiKeyRequest);
+ assertEquals(HttpGet.METHOD_NAME, request.getMethod());
+ assertEquals("/_security/api_key/my", request.getEndpoint());
+ Map mapOfParameters = new HashMap<>();
+ mapOfParameters.put("id", apiKeyId);
+ assertThat(request.getParameters(), equalTo(mapOfParameters));
+ }
+
+ public void testInvalidateMyApiKey() throws IOException {
+ String apiKeyId = randomAlphaOfLength(5);
+ final InvalidateMyApiKeyRequest invalidateApiKeyRequest = new InvalidateMyApiKeyRequest(apiKeyId, null);
+ final Request request = SecurityRequestConverters.invalidateMyApiKey(invalidateApiKeyRequest);
+ assertEquals(HttpDelete.METHOD_NAME, request.getMethod());
+ assertEquals("/_security/api_key/my", request.getEndpoint());
+ assertToXContentBody(invalidateApiKeyRequest, request.getEntity());
+ }
}
diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java
index f9a1c5c6571eb..135fd9a2f7099 100644
--- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java
+++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java
@@ -50,6 +50,7 @@
import org.elasticsearch.client.security.ExpressionRoleMapping;
import org.elasticsearch.client.security.GetApiKeyRequest;
import org.elasticsearch.client.security.GetApiKeyResponse;
+import org.elasticsearch.client.security.GetMyApiKeyRequest;
import org.elasticsearch.client.security.GetPrivilegesRequest;
import org.elasticsearch.client.security.GetPrivilegesResponse;
import org.elasticsearch.client.security.GetRoleMappingsRequest;
@@ -64,6 +65,7 @@
import org.elasticsearch.client.security.HasPrivilegesResponse;
import org.elasticsearch.client.security.InvalidateApiKeyRequest;
import org.elasticsearch.client.security.InvalidateApiKeyResponse;
+import org.elasticsearch.client.security.InvalidateMyApiKeyRequest;
import org.elasticsearch.client.security.InvalidateTokenRequest;
import org.elasticsearch.client.security.InvalidateTokenResponse;
import org.elasticsearch.client.security.PutPrivilegesRequest;
@@ -1962,6 +1964,97 @@ public void onFailure(Exception e) {
}
}
+ public void testGetMyApiKey() throws Exception {
+ RestHighLevelClient client = highLevelClient();
+
+ List roles = Collections.singletonList(Role.builder().name("r1").clusterPrivileges(ClusterPrivilegeName.ALL)
+ .indicesPrivileges(IndicesPrivileges.builder().indices("ind-x").privileges(IndexPrivilegeName.ALL).build()).build());
+ final TimeValue expiration = TimeValue.timeValueHours(24);
+ final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
+ // Create API Keys
+ CreateApiKeyRequest createApiKeyRequest = new CreateApiKeyRequest("k1", roles, expiration, refreshPolicy);
+ CreateApiKeyResponse createApiKeyResponse1 = client.security().createApiKey(createApiKeyRequest, RequestOptions.DEFAULT);
+ assertThat(createApiKeyResponse1.getName(), equalTo("k1"));
+ assertNotNull(createApiKeyResponse1.getKey());
+
+ final ApiKey expectedApiKeyInfo = new ApiKey(createApiKeyResponse1.getName(), createApiKeyResponse1.getId(), Instant.now(),
+ Instant.now().plusMillis(expiration.getMillis()), false, "test_user", "default_file");
+ {
+ // tag::get-my-api-key-id-request
+ GetMyApiKeyRequest getApiKeyRequest = GetMyApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId());
+ // end::get-my-api-key-id-request
+
+ // tag::get-my-api-key-execute
+ GetApiKeyResponse getApiKeyResponse = client.security().getMyApiKey(getApiKeyRequest, RequestOptions.DEFAULT);
+ // end::get-my-api-key-execute
+
+ assertThat(getApiKeyResponse.getApiKeyInfos(), is(notNullValue()));
+ assertThat(getApiKeyResponse.getApiKeyInfos().size(), is(1));
+ verifyApiKey(getApiKeyResponse.getApiKeyInfos().get(0), expectedApiKeyInfo);
+ }
+
+ {
+ // tag::get-my-api-key-name-request
+ GetMyApiKeyRequest getApiKeyRequest = GetMyApiKeyRequest.usingApiKeyName(createApiKeyResponse1.getName());
+ // end::get-my-api-key-name-request
+
+ GetApiKeyResponse getApiKeyResponse = client.security().getMyApiKey(getApiKeyRequest, RequestOptions.DEFAULT);
+
+ assertThat(getApiKeyResponse.getApiKeyInfos(), is(notNullValue()));
+ assertThat(getApiKeyResponse.getApiKeyInfos().size(), is(1));
+ verifyApiKey(getApiKeyResponse.getApiKeyInfos().get(0), expectedApiKeyInfo);
+ }
+
+ {
+ // tag::get-my-api-key-all-request
+ GetMyApiKeyRequest getApiKeyRequest = new GetMyApiKeyRequest(null, null);
+ // end::get-my-api-key-all-request
+
+ GetApiKeyResponse getApiKeyResponse = client.security().getMyApiKey(getApiKeyRequest, RequestOptions.DEFAULT);
+
+ assertThat(getApiKeyResponse.getApiKeyInfos(), is(notNullValue()));
+ assertThat(getApiKeyResponse.getApiKeyInfos().size(), is(1));
+ verifyApiKey(getApiKeyResponse.getApiKeyInfos().get(0), expectedApiKeyInfo);
+ }
+
+ {
+ GetMyApiKeyRequest getApiKeyRequest = GetMyApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId());
+
+ ActionListener listener;
+ // tag::get-my-api-key-execute-listener
+ listener = new ActionListener() {
+ @Override
+ public void onResponse(GetApiKeyResponse getApiKeyResponse) {
+ // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ // end::get-my-api-key-execute-listener
+
+ // Avoid unused variable warning
+ assertNotNull(listener);
+
+ // Replace the empty listener by a blocking listener in test
+ final PlainActionFuture future = new PlainActionFuture<>();
+ listener = future;
+
+ // tag::get-my-api-key-execute-async
+ client.security().getMyApiKeyAsync(getApiKeyRequest, RequestOptions.DEFAULT, listener); // <1>
+ // end::get-my-api-key-execute-async
+
+ final GetApiKeyResponse response = future.get(30, TimeUnit.SECONDS);
+ assertNotNull(response);
+
+ assertThat(response.getApiKeyInfos(), is(notNullValue()));
+ assertThat(response.getApiKeyInfos().size(), is(1));
+ verifyApiKey(response.getApiKeyInfos().get(0), expectedApiKeyInfo);
+ }
+ }
+
private void verifyApiKey(final ApiKey actual, final ApiKey expected) {
assertThat(actual.getId(), is(expected.getId()));
assertThat(actual.getName(), is(expected.getName()));
@@ -2141,4 +2234,127 @@ public void onFailure(Exception e) {
assertThat(response.getPreviouslyInvalidatedApiKeys().size(), equalTo(0));
}
}
+
+ public void testInvalidateMyApiKey() throws Exception {
+ RestHighLevelClient client = highLevelClient();
+
+ List roles = Collections.singletonList(Role.builder().name("r1").clusterPrivileges(ClusterPrivilegeName.ALL)
+ .indicesPrivileges(IndicesPrivileges.builder().indices("ind-x").privileges(IndexPrivilegeName.ALL).build()).build());
+ final TimeValue expiration = TimeValue.timeValueHours(24);
+ final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
+ // Create API Keys
+ CreateApiKeyRequest createApiKeyRequest = new CreateApiKeyRequest("k1", roles, expiration, refreshPolicy);
+ CreateApiKeyResponse createApiKeyResponse1 = client.security().createApiKey(createApiKeyRequest, RequestOptions.DEFAULT);
+ assertThat(createApiKeyResponse1.getName(), equalTo("k1"));
+ assertNotNull(createApiKeyResponse1.getKey());
+
+ {
+ // tag::invalidate-my-api-key-id-request
+ InvalidateMyApiKeyRequest invalidateApiKeyRequest = InvalidateMyApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId());
+ // end::invalidate-my-api-key-id-request
+
+ // tag::invalidate-my-api-key-execute
+ InvalidateApiKeyResponse invalidateApiKeyResponse = client.security().invalidateMyApiKey(invalidateApiKeyRequest,
+ RequestOptions.DEFAULT);
+ // end::invalidate-my-api-key-execute
+
+ final List errors = invalidateApiKeyResponse.getErrors();
+ final List invalidatedApiKeyIds = invalidateApiKeyResponse.getInvalidatedApiKeys();
+ final List previouslyInvalidatedApiKeyIds = invalidateApiKeyResponse.getPreviouslyInvalidatedApiKeys();
+
+ assertTrue(errors.isEmpty());
+ List expectedInvalidatedApiKeyIds = Arrays.asList(createApiKeyResponse1.getId());
+ assertThat(invalidatedApiKeyIds, containsInAnyOrder(expectedInvalidatedApiKeyIds.toArray(Strings.EMPTY_ARRAY)));
+ assertThat(previouslyInvalidatedApiKeyIds.size(), equalTo(0));
+ }
+
+ {
+ createApiKeyRequest = new CreateApiKeyRequest("k2", roles, expiration, refreshPolicy);
+ CreateApiKeyResponse createApiKeyResponse2 = client.security().createApiKey(createApiKeyRequest, RequestOptions.DEFAULT);
+ assertThat(createApiKeyResponse2.getName(), equalTo("k2"));
+ assertNotNull(createApiKeyResponse2.getKey());
+
+ // tag::invalidate-my-api-key-name-request
+ InvalidateMyApiKeyRequest invalidateApiKeyRequest = InvalidateMyApiKeyRequest.usingApiKeyName(createApiKeyResponse2.getName());
+ // end::invalidate-my-api-key-name-request
+
+ InvalidateApiKeyResponse invalidateApiKeyResponse = client.security().invalidateMyApiKey(invalidateApiKeyRequest,
+ RequestOptions.DEFAULT);
+
+ final List errors = invalidateApiKeyResponse.getErrors();
+ final List invalidatedApiKeyIds = invalidateApiKeyResponse.getInvalidatedApiKeys();
+ final List previouslyInvalidatedApiKeyIds = invalidateApiKeyResponse.getPreviouslyInvalidatedApiKeys();
+
+ assertTrue(errors.isEmpty());
+ List expectedInvalidatedApiKeyIds = Arrays.asList(createApiKeyResponse2.getId());
+ assertThat(invalidatedApiKeyIds, containsInAnyOrder(expectedInvalidatedApiKeyIds.toArray(Strings.EMPTY_ARRAY)));
+ assertThat(previouslyInvalidatedApiKeyIds.size(), equalTo(0));
+ }
+
+ {
+ createApiKeyRequest = new CreateApiKeyRequest("k3", roles, expiration, refreshPolicy);
+ CreateApiKeyResponse createApiKeyResponse3 = client.security().createApiKey(createApiKeyRequest, RequestOptions.DEFAULT);
+ assertThat(createApiKeyResponse3.getName(), equalTo("k3"));
+ assertNotNull(createApiKeyResponse3.getKey());
+
+ // tag::invalidate-my-api-key-all-request
+ InvalidateMyApiKeyRequest invalidateApiKeyRequest = new InvalidateMyApiKeyRequest(null, null);
+ // end::invalidate-my-api-key-all-request
+
+ InvalidateApiKeyResponse invalidateApiKeyResponse = client.security().invalidateMyApiKey(invalidateApiKeyRequest,
+ RequestOptions.DEFAULT);
+
+ final List errors = invalidateApiKeyResponse.getErrors();
+ final List invalidatedApiKeyIds = invalidateApiKeyResponse.getInvalidatedApiKeys();
+ final List previouslyInvalidatedApiKeyIds = invalidateApiKeyResponse.getPreviouslyInvalidatedApiKeys();
+
+ assertTrue(errors.isEmpty());
+ List expectedInvalidatedApiKeyIds = Arrays.asList(createApiKeyResponse3.getId());
+ assertThat(invalidatedApiKeyIds, containsInAnyOrder(expectedInvalidatedApiKeyIds.toArray(Strings.EMPTY_ARRAY)));
+ assertThat(previouslyInvalidatedApiKeyIds.size(), equalTo(0));
+ }
+
+ {
+ createApiKeyRequest = new CreateApiKeyRequest("k6", roles, expiration, refreshPolicy);
+ CreateApiKeyResponse createApiKeyResponse6 = client.security().createApiKey(createApiKeyRequest, RequestOptions.DEFAULT);
+ assertThat(createApiKeyResponse6.getName(), equalTo("k6"));
+ assertNotNull(createApiKeyResponse6.getKey());
+
+ InvalidateMyApiKeyRequest invalidateApiKeyRequest = InvalidateMyApiKeyRequest.usingApiKeyId(createApiKeyResponse6.getId());
+
+ ActionListener listener;
+ // tag::invalidate-my-api-key-execute-listener
+ listener = new ActionListener() {
+ @Override
+ public void onResponse(InvalidateApiKeyResponse invalidateApiKeyResponse) {
+ // <1>
+ }
+
+ @Override
+ public void onFailure(Exception e) {
+ // <2>
+ }
+ };
+ // end::invalidate-my-api-key-execute-listener
+
+ // Avoid unused variable warning
+ assertNotNull(listener);
+
+ // Replace the empty listener by a blocking listener in test
+ final PlainActionFuture future = new PlainActionFuture<>();
+ listener = future;
+
+ // tag::invalidate-my-api-key-execute-async
+ client.security().invalidateMyApiKeyAsync(invalidateApiKeyRequest, RequestOptions.DEFAULT, listener); // <1>
+ // end::invalidate-my-api-key-execute-async
+
+ final InvalidateApiKeyResponse response = future.get(30, TimeUnit.SECONDS);
+ assertNotNull(response);
+ final List invalidatedApiKeyIds = response.getInvalidatedApiKeys();
+ List expectedInvalidatedApiKeyIds = Arrays.asList(createApiKeyResponse6.getId());
+ assertTrue(response.getErrors().isEmpty());
+ assertThat(invalidatedApiKeyIds, containsInAnyOrder(expectedInvalidatedApiKeyIds.toArray(Strings.EMPTY_ARRAY)));
+ assertThat(response.getPreviouslyInvalidatedApiKeys().size(), equalTo(0));
+ }
+ }
}
diff --git a/docs/java-rest/high-level/security/get-my-api-key.asciidoc b/docs/java-rest/high-level/security/get-my-api-key.asciidoc
new file mode 100644
index 0000000000000..9cb07dbdccb9b
--- /dev/null
+++ b/docs/java-rest/high-level/security/get-my-api-key.asciidoc
@@ -0,0 +1,51 @@
+--
+:api: get-my-api-key
+:request: GetMyApiKeyRequest
+:response: GetMyApiKeyResponse
+--
+
+[id="{upid}-{api}"]
+=== Get information for API key(s) owned by the authenticated user API
+
+API Key(s) information owned by the authenticated user can be retrieved using this API.
+
+[id="{upid}-{api}-request"]
+==== Get My API Key Request
+The +{request}+ supports retrieving API key information for
+
+. A specific API key by id or by name
+
+. All API key(s) of the current authenticated user
+
+===== Retrieve information for a specific API key by its id
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[get-my-api-key-id-request]
+--------------------------------------------------
+
+===== Retrieve information for a specific API key by its name
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[get-my-api-key-name-request]
+--------------------------------------------------
+
+===== Retrieve information for all API key(s) owned by the authenticated user
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[get-my-api-key-all-request]
+--------------------------------------------------
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Get My API Key information API Response
+
+The returned +{response}+ contains the information regarding the API keys that were
+requested.
+
+`api_keys`:: Available using `getApiKeyInfos`, contains list of API keys that were retrieved for this request.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
diff --git a/docs/java-rest/high-level/security/invalidate-my-api-key.asciidoc b/docs/java-rest/high-level/security/invalidate-my-api-key.asciidoc
new file mode 100644
index 0000000000000..cbe95a4b7e3d8
--- /dev/null
+++ b/docs/java-rest/high-level/security/invalidate-my-api-key.asciidoc
@@ -0,0 +1,59 @@
+--
+:api: invalidate-my-api-key
+:request: InvalidateMyApiKeyRequest
+:response: InvalidateMyApiKeyResponse
+--
+
+[id="{upid}-{api}"]
+=== Invalidate API key(s) owned by the authenticated user API
+
+API Key(s) owned by the authenticated user can be invalidated using this API.
+
+[id="{upid}-{api}-request"]
+==== Invalidate My API Key Request
+The +{request}+ supports invalidating
+
+. A specific API key by id or by name
+
+. All API key(s) of the current authenticated user
+
+===== Specific API key by API key id
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[invalidate-my-api-key-id-request]
+--------------------------------------------------
+
+===== Specific API key by API key name
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[invalidate-my-api-key-name-request]
+--------------------------------------------------
+
+===== Invalidate all API key(s) owned by authenticated user
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[invalidate-my-api-key-all-request]
+--------------------------------------------------
+
+include::../execution.asciidoc[]
+
+[id="{upid}-{api}-response"]
+==== Invalidate API Key Response
+
+The returned +{response}+ contains the information regarding the API keys that the request
+invalidated.
+
+`invalidatedApiKeys`:: Available using `getInvalidatedApiKeys` lists the API keys
+ that this request invalidated.
+
+`previouslyInvalidatedApiKeys`:: Available using `getPreviouslyInvalidatedApiKeys` lists the API keys
+ that this request attempted to invalidate
+ but were already invalid.
+
+`errors`:: Available using `getErrors` contains possible errors that were encountered while
+ attempting to invalidate API keys.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[{api}-response]
+--------------------------------------------------
\ No newline at end of file
diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc
index 4e28efc2941db..d4190c21f22ba 100644
--- a/docs/java-rest/high-level/supported-apis.asciidoc
+++ b/docs/java-rest/high-level/supported-apis.asciidoc
@@ -409,7 +409,9 @@ The Java High Level REST Client supports the following Security APIs:
* <<{upid}-delete-privileges>>
* <<{upid}-create-api-key>>
* <<{upid}-get-api-key>>
+* <<{upid}-get-my-api-key>>
* <<{upid}-invalidate-api-key>>
+* <<{upid}-invalidate-my-api-key>>
include::security/put-user.asciidoc[]
include::security/get-users.asciidoc[]
diff --git a/x-pack/docs/en/rest-api/security.asciidoc b/x-pack/docs/en/rest-api/security.asciidoc
index c04bae90801ee..efc8a5cdcbbc7 100644
--- a/x-pack/docs/en/rest-api/security.asciidoc
+++ b/x-pack/docs/en/rest-api/security.asciidoc
@@ -60,7 +60,9 @@ without requiring basic authentication:
* <>
* <>
+* <>
* <>
+* <>
[float]
[[security-user-apis]]
@@ -102,6 +104,7 @@ include::security/delete-users.asciidoc[]
include::security/disable-users.asciidoc[]
include::security/enable-users.asciidoc[]
include::security/get-api-keys.asciidoc[]
+include::security/get-my-api-keys.asciidoc[]
include::security/get-app-privileges.asciidoc[]
include::security/get-role-mappings.asciidoc[]
include::security/get-roles.asciidoc[]
@@ -109,6 +112,7 @@ include::security/get-tokens.asciidoc[]
include::security/get-users.asciidoc[]
include::security/has-privileges.asciidoc[]
include::security/invalidate-api-keys.asciidoc[]
+include::security/invalidate-my-api-keys.asciidoc[]
include::security/invalidate-tokens.asciidoc[]
include::security/ssl.asciidoc[]
include::security/oidc-prepare-authentication-api.asciidoc[]
diff --git a/x-pack/docs/en/rest-api/security/get-my-api-keys.asciidoc b/x-pack/docs/en/rest-api/security/get-my-api-keys.asciidoc
new file mode 100644
index 0000000000000..821e0ec4fb4e0
--- /dev/null
+++ b/x-pack/docs/en/rest-api/security/get-my-api-keys.asciidoc
@@ -0,0 +1,125 @@
+[role="xpack"]
+[[security-api-get-my-api-key]]
+=== Get information for API key(s) owned by the authenticated user API
+++++
+Get information for API key(s) owned by the authenticated user API
+++++
+
+Retrieves information for one or more API keys owned by the authenticated user
+
+==== Request
+
+`GET /_security/api_key/my`
+
+==== Description
+
+The information for the API keys created by <> can be retrieved
+using this API. This API can only retrieve API key information for the API keys owned by the current authenticated user.
+
+==== Request Body
+
+The following parameters can be specified in the query parameters of a GET request and
+pertain to retrieving api keys:
+
+`id` (optional)::
+(string) An API key id.
+
+`name` (optional)::
+(string) An API key name.
+
+NOTE: If none of the parameters are set, it will get information for all API keys owned by the current authenticated user.
+
+==== Examples
+
+If you create an API key as follows:
+
+[source, js]
+------------------------------------------------------------
+POST /_security/api_key
+{
+ "name": "my-api-key",
+ "role_descriptors": {}
+}
+------------------------------------------------------------
+// CONSOLE
+// TEST
+
+A successful call returns a JSON structure that provides
+API key information. For example:
+
+[source,js]
+--------------------------------------------------
+{
+ "id":"VuaCfGcBCdbkQm-e5aOx",
+ "name":"my-api-key",
+ "api_key":"ui2lp2axTNmsyakw9tvNnw"
+}
+--------------------------------------------------
+// TESTRESPONSE[s/VuaCfGcBCdbkQm-e5aOx/$body.id/]
+// TESTRESPONSE[s/ui2lp2axTNmsyakw9tvNnw/$body.api_key/]
+
+You can use the following example to retrieve the API key by ID:
+
+[source,js]
+--------------------------------------------------
+GET /_security/api_key/my?id=VuaCfGcBCdbkQm-e5aOx
+--------------------------------------------------
+// CONSOLE
+// TEST[s/VuaCfGcBCdbkQm-e5aOx/$body.id/]
+// TEST[continued]
+
+You can use the following example to retrieve the API key by name:
+
+[source,js]
+--------------------------------------------------
+GET /_security/api_key/my?name=my-api-key
+--------------------------------------------------
+// CONSOLE
+// TEST[continued]
+
+The following example retrieves all API keys owned by the current authenticated user:
+
+[source,js]
+--------------------------------------------------
+GET /_security/api_key/my
+--------------------------------------------------
+// CONSOLE
+// TEST[continued]
+
+A successful call returns a JSON structure that contains the information of one or more API keys that were retrieved.
+
+[source,js]
+--------------------------------------------------
+{
+ "api_keys": [ <1>
+ {
+ "id": "dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvbmx5IHRlc3QgZGF0YS4gZG8gbm90IHRyeSB0byByZWFkIHRva2VuIQ==", <2>
+ "name": "hadoop_myuser_key", <3>
+ "creation": 1548550550158, <4>
+ "expiration": 1548551550158, <5>
+ "invalidated": false, <6>
+ "username": "myuser", <7>
+ "realm": "native1" <8>
+ },
+ {
+ "id": "api-key-id-2",
+ "name": "api-key-name-2",
+ "creation": 1548550550158,
+ "invalidated": false,
+ "username": "user-y",
+ "realm": "realm-2"
+ }
+ ]
+}
+--------------------------------------------------
+// NOTCONSOLE
+
+<1> The list of API keys that were retrieved for this request.
+<2> Id for the API key
+<3> Name of the API key
+<4> Creation time for the API key in milliseconds
+<5> Optional expiration time for the API key in milliseconds
+<6> Invalidation status for the API key. If the key has been invalidated, it has
+a value of `true`. Otherwise, it is `false`.
+<7> Principal for which this API key was created
+<8> Realm name of the principal for which this API key was created
diff --git a/x-pack/docs/en/rest-api/security/invalidate-my-api-keys.asciidoc b/x-pack/docs/en/rest-api/security/invalidate-my-api-keys.asciidoc
new file mode 100644
index 0000000000000..5f208fe43804e
--- /dev/null
+++ b/x-pack/docs/en/rest-api/security/invalidate-my-api-keys.asciidoc
@@ -0,0 +1,140 @@
+[role="xpack"]
+[[security-api-invalidate-my-api-key]]
+=== Invalidate API key(s) owned by the authenticated user API
+++++
+Invalidate API key(s) owned by the authenticated user API
+++++
+
+Invalidates one or more API keys owned by the authenticated user
+
+==== Request
+
+`DELETE /_security/api_key/my`
+
+==== Description
+
+The API keys created by <> can be
+invalidated using this API.
+
+==== Request Body
+
+The following parameters can be specified in the body of a DELETE request and
+pertain to invalidating api keys:
+
+`id` (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.
+
+NOTE: If none of the parameters are set, it will invalidate API keys owned by the authenticated user.
+
+==== Examples
+
+If you create an API key as follows:
+
+[source, js]
+------------------------------------------------------------
+POST /_security/api_key
+{
+ "name": "my-api-key",
+ "role_descriptors": {}
+}
+------------------------------------------------------------
+// CONSOLE
+// TEST
+
+A successful call returns a JSON structure that provides
+API key information. For example:
+
+[source,js]
+--------------------------------------------------
+{
+ "id":"VuaCfGcBCdbkQm-e5aOx",
+ "name":"my-api-key",
+ "api_key":"ui2lp2axTNmsyakw9tvNnw"
+}
+--------------------------------------------------
+// TESTRESPONSE[s/VuaCfGcBCdbkQm-e5aOx/$body.id/]
+// TESTRESPONSE[s/ui2lp2axTNmsyakw9tvNnw/$body.api_key/]
+
+The following example invalidates the API key identified by specified `id` immediately:
+
+[source,js]
+--------------------------------------------------
+DELETE /_security/api_key/my
+{
+ "id" : "VuaCfGcBCdbkQm-e5aOx"
+}
+--------------------------------------------------
+// CONSOLE
+// TEST[s/VuaCfGcBCdbkQm-e5aOx/$body.id/]
+// TEST[continued]
+
+The following example invalidates the API key identified by specified `name` immediately:
+
+[source,js]
+--------------------------------------------------
+DELETE /_security/api_key/my
+{
+ "name" : "my-api-key"
+}
+--------------------------------------------------
+// CONSOLE
+// TEST
+
+The following example invalidates all API keys owned by the authenticated user immediately:
+
+[source,js]
+--------------------------------------------------
+DELETE /_security/api_key/my
+{
+}
+--------------------------------------------------
+// CONSOLE
+// TEST
+
+A successful call returns a JSON structure that contains the ids of the API keys that were invalidated, the ids
+of the API keys that had already been invalidated, and potentially a list of errors encountered while invalidating
+specific api keys.
+
+[source,js]
+--------------------------------------------------
+{
+ "invalidated_api_keys": [ <1>
+ "api-key-id-1"
+ ],
+ "previously_invalidated_api_keys": [ <2>
+ "api-key-id-2",
+ "api-key-id-3"
+ ],
+ "error_count": 2, <3>
+ "error_details": [ <4>
+ {
+ "type": "exception",
+ "reason": "error occurred while invalidating api keys",
+ "caused_by": {
+ "type": "illegal_argument_exception",
+ "reason": "invalid api key id"
+ }
+ },
+ {
+ "type": "exception",
+ "reason": "error occurred while invalidating api keys",
+ "caused_by": {
+ "type": "illegal_argument_exception",
+ "reason": "invalid api key id"
+ }
+ }
+ ]
+}
+--------------------------------------------------
+// NOTCONSOLE
+
+<1> The ids of the API keys that were invalidated as part of this request.
+<2> The ids of the API keys that were already invalidated.
+<3> The number of errors that were encountered when invalidating the API keys.
+<4> Details about these errors. This field is not present in the response when
+ `error_count` is 0.
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java
index a145569898ee6..0cbf2c4090c29 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/XPackClientPlugin.java
@@ -151,7 +151,9 @@
import org.elasticsearch.xpack.core.security.SecuritySettings;
import org.elasticsearch.xpack.core.security.action.CreateApiKeyAction;
import org.elasticsearch.xpack.core.security.action.GetApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyAction;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.InvalidateMyApiKeyAction;
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheAction;
import org.elasticsearch.xpack.core.security.action.role.ClearRolesCacheAction;
import org.elasticsearch.xpack.core.security.action.role.DeleteRoleAction;
@@ -332,6 +334,8 @@ public List> getClientActions() {
CreateApiKeyAction.INSTANCE,
InvalidateApiKeyAction.INSTANCE,
GetApiKeyAction.INSTANCE,
+ InvalidateMyApiKeyAction.INSTANCE,
+ GetMyApiKeyAction.INSTANCE,
// upgrade
IndexUpgradeInfoAction.INSTANCE,
IndexUpgradeAction.INSTANCE,
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequest.java
index 287ebcee4b6f2..f5a5e58e3a1c8 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequest.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequest.java
@@ -143,4 +143,10 @@ public void writeTo(StreamOutput out) throws IOException {
public void readFrom(StreamInput in) throws IOException {
throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
}
+
+ @Override
+ public String toString() {
+ return "GetApiKeyRequest [realmName=" + realmName + ", userName=" + userName + ", apiKeyId=" + apiKeyId + ", apiKeyName="
+ + apiKeyName + "]";
+ }
}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetMyApiKeyAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetMyApiKeyAction.java
new file mode 100644
index 0000000000000..77e979c67e8d3
--- /dev/null
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetMyApiKeyAction.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.core.security.action;
+
+import org.elasticsearch.action.Action;
+import org.elasticsearch.common.io.stream.Writeable;
+
+/**
+ * Action for retrieving API key(s) owned by the authenticated user
+ */
+public final class GetMyApiKeyAction extends Action {
+
+ public static final String NAME = "cluster:admin/xpack/security/api_key/get/my";
+ public static final GetMyApiKeyAction INSTANCE = new GetMyApiKeyAction();
+
+ private GetMyApiKeyAction() {
+ super(NAME);
+ }
+
+ @Override
+ public GetApiKeyResponse newResponse() {
+ throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
+ }
+
+ @Override
+ public Writeable.Reader getResponseReader() {
+ return GetApiKeyResponse::new;
+ }
+}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetMyApiKeyRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetMyApiKeyRequest.java
new file mode 100644
index 0000000000000..5f63a80fc1e69
--- /dev/null
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/GetMyApiKeyRequest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.core.security.action;
+
+import org.elasticsearch.action.ActionRequest;
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+
+import java.io.IOException;
+
+/**
+ * Request for retrieving information for API key(s) owned by the authenticated user.
+ */
+public final class GetMyApiKeyRequest extends ActionRequest {
+
+ private final String apiKeyId;
+ private final String apiKeyName;
+
+ public GetMyApiKeyRequest() {
+ this(null, null);
+ }
+
+ public GetMyApiKeyRequest(StreamInput in) throws IOException {
+ super(in);
+ apiKeyId = in.readOptionalString();
+ apiKeyName = in.readOptionalString();
+ }
+
+ public GetMyApiKeyRequest(@Nullable String apiKeyId, @Nullable String apiKeyName) {
+ this.apiKeyId = apiKeyId;
+ this.apiKeyName = apiKeyName;
+ }
+
+ public String getApiKeyId() {
+ return apiKeyId;
+ }
+
+ public String getApiKeyName() {
+ return apiKeyName;
+ }
+
+ /**
+ * Creates request for given api key id
+ * @param apiKeyId api key id
+ * @return {@link GetMyApiKeyRequest}
+ */
+ public static GetMyApiKeyRequest usingApiKeyId(String apiKeyId) {
+ return new GetMyApiKeyRequest(apiKeyId, null);
+ }
+
+ /**
+ * Creates request for given api key name
+ * @param apiKeyName api key name
+ * @return {@link GetMyApiKeyRequest}
+ */
+ public static GetMyApiKeyRequest usingApiKeyName(String apiKeyName) {
+ return new GetMyApiKeyRequest(null, apiKeyName);
+ }
+
+ @Override
+ public ActionRequestValidationException validate() {
+ return null;
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ super.writeTo(out);
+ out.writeOptionalString(apiKeyId);
+ out.writeOptionalString(apiKeyName);
+ }
+
+ @Override
+ public void readFrom(StreamInput in) throws IOException {
+ throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
+ }
+
+ @Override
+ public String toString() {
+ return "GetMyApiKeyRequest [apiKeyId=" + apiKeyId + ", apiKeyName=" + apiKeyName + "]";
+ }
+
+}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequest.java
index f8815785d53d8..50a63c502a1e5 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequest.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequest.java
@@ -143,4 +143,9 @@ public void writeTo(StreamOutput out) throws IOException {
public void readFrom(StreamInput in) throws IOException {
throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
}
+
+ @Override
+ public String toString() {
+ return "InvalidateApiKeyRequest [realmName=" + realmName + ", userName=" + userName + ", id=" + id + ", name=" + name + "]";
+ }
}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateMyApiKeyAction.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateMyApiKeyAction.java
new file mode 100644
index 0000000000000..0b63abe5cbaf7
--- /dev/null
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateMyApiKeyAction.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.core.security.action;
+
+import org.elasticsearch.action.Action;
+import org.elasticsearch.common.io.stream.Writeable;
+
+/**
+ * Action for invalidating API key for the authenticated user
+ */
+public final class InvalidateMyApiKeyAction extends Action {
+
+ public static final String NAME = "cluster:admin/xpack/security/api_key/invalidate/my";
+ public static final InvalidateMyApiKeyAction INSTANCE = new InvalidateMyApiKeyAction();
+
+ private InvalidateMyApiKeyAction() {
+ super(NAME);
+ }
+
+ @Override
+ public InvalidateApiKeyResponse newResponse() {
+ throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
+ }
+
+ @Override
+ public Writeable.Reader getResponseReader() {
+ return InvalidateApiKeyResponse::new;
+ }
+}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateMyApiKeyRequest.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateMyApiKeyRequest.java
new file mode 100644
index 0000000000000..a4ee7233fa739
--- /dev/null
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/action/InvalidateMyApiKeyRequest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.core.security.action;
+
+import org.elasticsearch.action.ActionRequest;
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+
+import java.io.IOException;
+
+/**
+ * Request for invalidating API key(s) for the authenticated user so that it can no longer be used.
+ */
+public final class InvalidateMyApiKeyRequest extends ActionRequest {
+
+ private final String id;
+ private final String name;
+
+ public InvalidateMyApiKeyRequest() {
+ this(null, null);
+ }
+
+ public InvalidateMyApiKeyRequest(StreamInput in) throws IOException {
+ super(in);
+ id = in.readOptionalString();
+ name = in.readOptionalString();
+ }
+
+ public InvalidateMyApiKeyRequest(@Nullable String id, @Nullable String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Creates invalidate API key request for given api key id
+ * @param id api key id
+ * @return {@link InvalidateMyApiKeyRequest}
+ */
+ public static InvalidateMyApiKeyRequest usingApiKeyId(String id) {
+ return new InvalidateMyApiKeyRequest(id, null);
+ }
+
+ /**
+ * Creates invalidate api key request for given api key name
+ * @param name api key name
+ * @return {@link InvalidateMyApiKeyRequest}
+ */
+ public static InvalidateMyApiKeyRequest usingApiKeyName(String name) {
+ return new InvalidateMyApiKeyRequest(null, name);
+ }
+
+ @Override
+ public ActionRequestValidationException validate() {
+ return null;
+ }
+
+ @Override
+ public void writeTo(StreamOutput out) throws IOException {
+ super.writeTo(out);
+ out.writeOptionalString(id);
+ out.writeOptionalString(name);
+ }
+
+ @Override
+ public void readFrom(StreamInput in) throws IOException {
+ throw new UnsupportedOperationException("usage of Streamable is to be replaced by Writeable");
+ }
+
+ @Override
+ public String toString() {
+ return "InvalidateMyApiKeyRequest [id=" + id + ", name=" + name + "]";
+ }
+
+}
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java
index f7d03c2356e5b..b85c0cb7ff453 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilege.java
@@ -37,6 +37,11 @@ public final class ClusterPrivilege extends Privilege {
private static final Automaton MANAGE_SECURITY_AUTOMATON = patterns("cluster:admin/xpack/security/*");
private static final Automaton MANAGE_SAML_AUTOMATON = patterns("cluster:admin/xpack/security/saml/*",
InvalidateTokenAction.NAME, RefreshTokenAction.NAME);
+ private static final Automaton MANAGE_API_KEY_AUTOMATON = patterns("cluster:admin/xpack/security/api_key/*");
+ private static final Automaton OWNER_MANAGE_API_KEY_AUTOMATON = patterns("cluster:admin/xpack/security/api_key/create",
+ "cluster:admin/xpack/security/api_key/invalidate/my", "cluster:admin/xpack/security/api_key/get/my");
+ private static final Automaton CREATE_API_KEY_AUTOMATON = patterns("cluster:admin/xpack/security/api_key/create",
+ "cluster:admin/xpack/security/api_key/get/my");
private static final Automaton MANAGE_OIDC_AUTOMATON = patterns("cluster:admin/xpack/security/oidc/*");
private static final Automaton MANAGE_TOKEN_AUTOMATON = patterns("cluster:admin/xpack/security/token/*");
private static final Automaton MONITOR_AUTOMATON = patterns("cluster:monitor/*");
@@ -73,6 +78,10 @@ public final class ClusterPrivilege extends Privilege {
public static final ClusterPrivilege MANAGE_ML = new ClusterPrivilege("manage_ml", MANAGE_ML_AUTOMATON);
public static final ClusterPrivilege MANAGE_DATA_FRAME =
new ClusterPrivilege("manage_data_frame_transforms", MANAGE_DATA_FRAME_AUTOMATON);
+ public static final ClusterPrivilege MANAGE_API_KEY = new ClusterPrivilege("manage_api_key", MANAGE_API_KEY_AUTOMATON);
+ public static final ClusterPrivilege OWNER_MANAGE_API_KEY = new ClusterPrivilege("owner_manage_api_key",
+ OWNER_MANAGE_API_KEY_AUTOMATON);
+ public static final ClusterPrivilege CREATE_API_KEY = new ClusterPrivilege("create_api_key", CREATE_API_KEY_AUTOMATON);
public static final ClusterPrivilege MANAGE_TOKEN = new ClusterPrivilege("manage_token", MANAGE_TOKEN_AUTOMATON);
public static final ClusterPrivilege MANAGE_WATCHER = new ClusterPrivilege("manage_watcher", MANAGE_WATCHER_AUTOMATON);
public static final ClusterPrivilege MANAGE_ROLLUP = new ClusterPrivilege("manage_rollup", MANAGE_ROLLUP_AUTOMATON);
@@ -104,6 +113,9 @@ public final class ClusterPrivilege extends Privilege {
entry("manage", MANAGE),
entry("manage_ml", MANAGE_ML),
entry("manage_data_frame_transforms", MANAGE_DATA_FRAME),
+ entry("manage_api_key", MANAGE_API_KEY),
+ entry("owner_manage_api_key", OWNER_MANAGE_API_KEY),
+ entry("create_api_key", CREATE_API_KEY),
entry("manage_token", MANAGE_TOKEN),
entry("manage_watcher", MANAGE_WATCHER),
entry("manage_index_templates", MANAGE_IDX_TEMPLATES),
diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/client/SecurityClient.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/client/SecurityClient.java
index 4619035d0daaf..c99d1ba034c4f 100644
--- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/client/SecurityClient.java
+++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/client/SecurityClient.java
@@ -17,9 +17,13 @@
import org.elasticsearch.xpack.core.security.action.GetApiKeyAction;
import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.GetApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyAction;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.InvalidateMyApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.InvalidateMyApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesAction;
import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesRequestBuilder;
import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesAction;
@@ -361,10 +365,18 @@ public void invalidateApiKey(InvalidateApiKeyRequest request, ActionListener listener) {
+ client.execute(InvalidateMyApiKeyAction.INSTANCE, request, listener);
+ }
+
public void getApiKey(GetApiKeyRequest request, ActionListener listener) {
client.execute(GetApiKeyAction.INSTANCE, request, listener);
}
+ public void getMyApiKey(GetMyApiKeyRequest request, ActionListener listener) {
+ client.execute(GetMyApiKeyAction.INSTANCE, request, listener);
+ }
+
public SamlAuthenticateRequestBuilder prepareSamlAuthenticate(byte[] xmlContent, List validIds) {
final SamlAuthenticateRequestBuilder builder = new SamlAuthenticateRequestBuilder(client);
builder.saml(xmlContent);
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 a36a004c7f413..2366df964618d 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
@@ -79,7 +79,9 @@
import org.elasticsearch.xpack.core.security.SecuritySettings;
import org.elasticsearch.xpack.core.security.action.CreateApiKeyAction;
import org.elasticsearch.xpack.core.security.action.GetApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyAction;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.InvalidateMyApiKeyAction;
import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectAuthenticateAction;
import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectLogoutAction;
import org.elasticsearch.xpack.core.security.action.oidc.OpenIdConnectPrepareAuthenticationAction;
@@ -136,7 +138,9 @@
import org.elasticsearch.xpack.core.ssl.rest.RestGetCertificateInfoAction;
import org.elasticsearch.xpack.security.action.TransportCreateApiKeyAction;
import org.elasticsearch.xpack.security.action.TransportGetApiKeyAction;
+import org.elasticsearch.xpack.security.action.TransportGetMyApiKeyAction;
import org.elasticsearch.xpack.security.action.TransportInvalidateApiKeyAction;
+import org.elasticsearch.xpack.security.action.TransportInvalidateMyApiKeyAction;
import org.elasticsearch.xpack.security.action.filter.SecurityActionFilter;
import org.elasticsearch.xpack.security.action.oidc.TransportOpenIdConnectAuthenticateAction;
import org.elasticsearch.xpack.security.action.oidc.TransportOpenIdConnectLogoutAction;
@@ -197,7 +201,9 @@
import org.elasticsearch.xpack.security.rest.action.RestAuthenticateAction;
import org.elasticsearch.xpack.security.rest.action.apikey.RestCreateApiKeyAction;
import org.elasticsearch.xpack.security.rest.action.apikey.RestGetApiKeyAction;
+import org.elasticsearch.xpack.security.rest.action.apikey.RestGetMyApiKeyAction;
import org.elasticsearch.xpack.security.rest.action.apikey.RestInvalidateApiKeyAction;
+import org.elasticsearch.xpack.security.rest.action.apikey.RestInvalidateMyApiKeyAction;
import org.elasticsearch.xpack.security.rest.action.oauth2.RestGetTokenAction;
import org.elasticsearch.xpack.security.rest.action.oauth2.RestInvalidateTokenAction;
import org.elasticsearch.xpack.security.rest.action.oidc.RestOpenIdConnectAuthenticateAction;
@@ -764,7 +770,9 @@ public void onIndexModule(IndexModule module) {
new ActionHandler<>(DeletePrivilegesAction.INSTANCE, TransportDeletePrivilegesAction.class),
new ActionHandler<>(CreateApiKeyAction.INSTANCE, TransportCreateApiKeyAction.class),
new ActionHandler<>(InvalidateApiKeyAction.INSTANCE, TransportInvalidateApiKeyAction.class),
- new ActionHandler<>(GetApiKeyAction.INSTANCE, TransportGetApiKeyAction.class)
+ new ActionHandler<>(InvalidateMyApiKeyAction.INSTANCE, TransportInvalidateMyApiKeyAction.class),
+ new ActionHandler<>(GetApiKeyAction.INSTANCE, TransportGetApiKeyAction.class),
+ new ActionHandler<>(GetMyApiKeyAction.INSTANCE, TransportGetMyApiKeyAction.class)
);
}
@@ -819,7 +827,9 @@ public List getRestHandlers(Settings settings, RestController restC
new RestDeletePrivilegesAction(settings, restController, getLicenseState()),
new RestCreateApiKeyAction(settings, restController, getLicenseState()),
new RestInvalidateApiKeyAction(settings, restController, getLicenseState()),
- new RestGetApiKeyAction(settings, restController, getLicenseState())
+ new RestInvalidateMyApiKeyAction(settings, restController, getLicenseState()),
+ new RestGetApiKeyAction(settings, restController, getLicenseState()),
+ new RestGetMyApiKeyAction(settings, restController, getLicenseState())
);
}
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportGetApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportGetApiKeyAction.java
index 403ce482805a2..7c010111eaec4 100644
--- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportGetApiKeyAction.java
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportGetApiKeyAction.java
@@ -9,7 +9,6 @@
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
-import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.tasks.Task;
@@ -32,15 +31,7 @@ public TransportGetApiKeyAction(TransportService transportService, ActionFilters
@Override
protected void doExecute(Task task, GetApiKeyRequest request, ActionListener listener) {
- if (Strings.hasText(request.getRealmName()) || Strings.hasText(request.getUserName())) {
- apiKeyService.getApiKeysForRealmAndUser(request.getRealmName(), request.getUserName(), listener);
- } else if (Strings.hasText(request.getApiKeyId())) {
- apiKeyService.getApiKeyForApiKeyId(request.getApiKeyId(), listener);
- } else if (Strings.hasText(request.getApiKeyName())) {
- apiKeyService.getApiKeyForApiKeyName(request.getApiKeyName(), listener);
- } else {
- listener.onFailure(new IllegalArgumentException("One of [api key id, api key name, username, realm name] must be specified"));
- }
+ apiKeyService.getApiKeys(request, listener);
}
}
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportGetMyApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportGetMyApiKeyAction.java
new file mode 100644
index 0000000000000..5f11cae062151
--- /dev/null
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportGetMyApiKeyAction.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.security.action;
+
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.action.support.HandledTransportAction;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.tasks.Task;
+import org.elasticsearch.transport.TransportService;
+import org.elasticsearch.xpack.core.security.action.GetApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyRequest;
+import org.elasticsearch.xpack.security.authc.ApiKeyService;
+
+public final class TransportGetMyApiKeyAction extends HandledTransportAction {
+
+ private final ApiKeyService apiKeyService;
+
+ @Inject
+ public TransportGetMyApiKeyAction(TransportService transportService, ActionFilters actionFilters, ApiKeyService apiKeyService) {
+ super(GetMyApiKeyAction.NAME, transportService, actionFilters, (Writeable.Reader) GetMyApiKeyRequest::new);
+ this.apiKeyService = apiKeyService;
+ }
+
+ @Override
+ protected void doExecute(Task task, GetMyApiKeyRequest request, ActionListener listener) {
+ apiKeyService.getApiKeysForCurrentUser(request, listener);
+ }
+
+}
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportInvalidateApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportInvalidateApiKeyAction.java
index 886d15b1f257d..5b32e5b0e81c5 100644
--- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportInvalidateApiKeyAction.java
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportInvalidateApiKeyAction.java
@@ -9,7 +9,6 @@
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.support.ActionFilters;
import org.elasticsearch.action.support.HandledTransportAction;
-import org.elasticsearch.common.Strings;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.tasks.Task;
@@ -32,13 +31,7 @@ public TransportInvalidateApiKeyAction(TransportService transportService, Action
@Override
protected void doExecute(Task task, InvalidateApiKeyRequest request, ActionListener listener) {
- if (Strings.hasText(request.getRealmName()) || Strings.hasText(request.getUserName())) {
- apiKeyService.invalidateApiKeysForRealmAndUser(request.getRealmName(), request.getUserName(), listener);
- } else if (Strings.hasText(request.getId())) {
- apiKeyService.invalidateApiKeyForApiKeyId(request.getId(), listener);
- } else {
- apiKeyService.invalidateApiKeyForApiKeyName(request.getName(), listener);
- }
+ apiKeyService.invalidateApiKeys(request, listener);
}
}
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportInvalidateMyApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportInvalidateMyApiKeyAction.java
new file mode 100644
index 0000000000000..84944fa0138e2
--- /dev/null
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/TransportInvalidateMyApiKeyAction.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.security.action;
+
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.action.support.ActionFilters;
+import org.elasticsearch.action.support.HandledTransportAction;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.io.stream.Writeable;
+import org.elasticsearch.tasks.Task;
+import org.elasticsearch.transport.TransportService;
+import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.InvalidateMyApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.InvalidateMyApiKeyRequest;
+import org.elasticsearch.xpack.security.authc.ApiKeyService;
+
+public final class TransportInvalidateMyApiKeyAction extends HandledTransportAction {
+
+ private final ApiKeyService apiKeyService;
+
+ @Inject
+ public TransportInvalidateMyApiKeyAction(TransportService transportService, ActionFilters actionFilters, ApiKeyService apiKeyService) {
+ super(InvalidateMyApiKeyAction.NAME, transportService, actionFilters,
+ (Writeable.Reader) InvalidateMyApiKeyRequest::new);
+ this.apiKeyService = apiKeyService;
+ }
+
+ @Override
+ protected void doExecute(Task task, InvalidateMyApiKeyRequest request, ActionListener listener) {
+ apiKeyService.invalidateApiKeysForCurrentUser(request, listener);
+ }
+
+}
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 74d4cce8d02de..15170b4e99e6b 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
@@ -62,8 +62,12 @@
import org.elasticsearch.xpack.core.security.action.ApiKey;
import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.CreateApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.GetApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyRequest;
+import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.InvalidateMyApiKeyRequest;
import org.elasticsearch.xpack.core.security.authc.Authentication;
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
import org.elasticsearch.xpack.core.security.authc.support.Hasher;
@@ -639,97 +643,90 @@ public void usedDeprecatedField(String usedName, String replacedWith) {
}
}
- /**
- * Invalidate API keys for given realm and user name.
- * @param realmName realm name
- * @param userName user name
- * @param invalidateListener listener for {@link InvalidateApiKeyResponse}
- */
- public void invalidateApiKeysForRealmAndUser(String realmName, String userName,
- ActionListener invalidateListener) {
- ensureEnabled();
- if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false) {
- logger.trace("No realm name or username provided");
- invalidateListener.onFailure(new IllegalArgumentException("realm name or username must be provided"));
- } else {
- findApiKeysForUserAndRealm(userName, realmName, true, false, ActionListener.wrap(apiKeyIds -> {
- if (apiKeyIds.isEmpty()) {
- logger.warn("No active api keys to invalidate for realm [{}] and username [{}]", realmName, userName);
- invalidateListener.onResponse(InvalidateApiKeyResponse.emptyResponse());
- } else {
- invalidateAllApiKeys(apiKeyIds.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()), invalidateListener);
- }
- }, invalidateListener::onFailure));
- }
- }
-
- private void invalidateAllApiKeys(Collection apiKeyIds, ActionListener invalidateListener) {
- indexInvalidation(apiKeyIds, invalidateListener, null);
- }
/**
- * Invalidate API key for given API key id
- * @param apiKeyId API key id
- * @param invalidateListener listener for {@link InvalidateApiKeyResponse}
+ * Invalidate API key(s) owned by the authenticated user.
+ * If no API key id or name is specified in the request then invalidates all API keys for current
+ * logged-in user.
+ *
+ * @param invalidateMyApiKeyRequest {@link InvalidateMyApiKeyRequest}
+ * @param listener listener for {@link InvalidateApiKeyResponse}
*/
- public void invalidateApiKeyForApiKeyId(String apiKeyId, ActionListener invalidateListener) {
+ public void invalidateApiKeysForCurrentUser(final InvalidateMyApiKeyRequest invalidateMyApiKeyRequest,
+ final ActionListener listener) {
ensureEnabled();
- if (Strings.hasText(apiKeyId) == false) {
- logger.trace("No api key id provided");
- invalidateListener.onFailure(new IllegalArgumentException("api key id must be provided"));
+
+ final Authentication authentication = Authentication.getAuthentication(threadPool.getThreadContext());
+ final String userName;
+ final String realmName;
+ final String apiKeyId;
+ final String apiKeyName;
+ if (authentication.getAuthenticatedBy().getType().equals("_es_api_key")) {
+ // in case of authenticated by API key, fetch key by API key id from authentication metadata
+ realmName = null;
+ userName = null;
+ apiKeyId = (String) authentication.getMetadata().get(API_KEY_ID_KEY);
+ apiKeyName = null;
} else {
- findApiKeysForApiKeyId(apiKeyId, true, false, ActionListener.wrap(apiKeyIds -> {
- if (apiKeyIds.isEmpty()) {
- logger.warn("No api key to invalidate for api key id [{}]", apiKeyId);
- invalidateListener.onResponse(InvalidateApiKeyResponse.emptyResponse());
- } else {
- invalidateAllApiKeys(apiKeyIds.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()), invalidateListener);
- }
- }, invalidateListener::onFailure));
- }
+ realmName = authentication.getLookedUpBy() == null ? authentication.getAuthenticatedBy().getName()
+ : authentication.getLookedUpBy().getName();
+ userName = authentication.getUser().principal();
+ apiKeyId = invalidateMyApiKeyRequest.getId();
+ apiKeyName = invalidateMyApiKeyRequest.getName();
+ }
+
+ findApiKeysForUserRealmApiKeyIdAndNameCombination(userName, realmName, apiKeyName, apiKeyId, true, false,
+ ActionListener.wrap(apiKeyIds -> {
+ if (apiKeyIds.isEmpty()) {
+ logger.warn("No api key to invalidate for {}", invalidateMyApiKeyRequest);
+ listener.onResponse(InvalidateApiKeyResponse.emptyResponse());
+ } else {
+ invalidateAllApiKeys(apiKeyIds.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()), listener);
+ }
+ }, listener::onFailure));
}
/**
- * Invalidate API key for given API key name
- * @param apiKeyName API key name
- * @param invalidateListener listener for {@link InvalidateApiKeyResponse}
+ * Invalidate API key(s).
+ * @param invalidateApiKeyRequest {@link InvalidateApiKeyRequest}
+ * @param listener listener for {@link InvalidateApiKeyResponse}
*/
- public void invalidateApiKeyForApiKeyName(String apiKeyName, ActionListener invalidateListener) {
+ public void invalidateApiKeys(final InvalidateApiKeyRequest invalidateApiKeyRequest,
+ final ActionListener listener) {
ensureEnabled();
- if (Strings.hasText(apiKeyName) == false) {
- logger.trace("No api key name provided");
- invalidateListener.onFailure(new IllegalArgumentException("api key name must be provided"));
- } else {
- findApiKeyForApiKeyName(apiKeyName, true, false, ActionListener.wrap(apiKeyIds -> {
- if (apiKeyIds.isEmpty()) {
- logger.warn("No api key to invalidate for api key name [{}]", apiKeyName);
- invalidateListener.onResponse(InvalidateApiKeyResponse.emptyResponse());
- } else {
- invalidateAllApiKeys(apiKeyIds.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()), invalidateListener);
- }
- }, invalidateListener::onFailure));
+ final String realmName = invalidateApiKeyRequest.getRealmName();
+ final String userName = invalidateApiKeyRequest.getUserName();
+ final String apiKeyId = invalidateApiKeyRequest.getId();
+ final String apiKeyName = invalidateApiKeyRequest.getName();
+
+ if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(apiKeyId) == false
+ && Strings.hasText(apiKeyName) == false) {
+ listener.onFailure(new IllegalArgumentException("one of [api key id, api key name, username, realm name] must be specified"));
+ }
+ if (Strings.hasText(apiKeyId) || Strings.hasText(apiKeyName)) {
+ if (Strings.hasText(realmName) || Strings.hasText(userName)) {
+ listener.onFailure(new IllegalArgumentException(
+ "username or realm name must not be specified when the api key id or api key name is specified"));
+ }
+ }
+ if (Strings.hasText(apiKeyId) && Strings.hasText(apiKeyName)) {
+ listener.onFailure(new IllegalArgumentException("only one of [api key id, api key name] can be specified"));
}
- }
- private void findApiKeysForUserAndRealm(String userName, String realmName, boolean filterOutInvalidatedKeys,
- boolean filterOutExpiredKeys, ActionListener> listener) {
- final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze();
- if (frozenSecurityIndex.indexExists() == false) {
- listener.onResponse(Collections.emptyList());
- } else if (frozenSecurityIndex.isAvailable() == false) {
- listener.onFailure(frozenSecurityIndex.getUnavailableReason());
- } else {
- final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
- .filter(QueryBuilders.termQuery("doc_type", "api_key"));
- if (Strings.hasText(userName)) {
- boolQuery.filter(QueryBuilders.termQuery("creator.principal", userName));
- }
- if (Strings.hasText(realmName)) {
- boolQuery.filter(QueryBuilders.termQuery("creator.realm", realmName));
- }
+ findApiKeysForUserRealmApiKeyIdAndNameCombination(userName, realmName, apiKeyName, apiKeyId, true, false,
+ ActionListener.wrap(apiKeyIds -> {
+ if (apiKeyIds.isEmpty()) {
+ logger.warn("No api key to invalidate for {}", invalidateApiKeyRequest);
+ listener.onResponse(InvalidateApiKeyResponse.emptyResponse());
+ } else {
+ invalidateAllApiKeys(apiKeyIds.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()),
+ listener);
+ }
+ }, listener::onFailure));
+ }
- findApiKeys(boolQuery, filterOutInvalidatedKeys, filterOutExpiredKeys, listener);
- }
+ private void invalidateAllApiKeys(Collection apiKeyIds, ActionListener invalidateListener) {
+ indexInvalidation(apiKeyIds, invalidateListener, null);
}
private void findApiKeys(final BoolQueryBuilder boolQuery, boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys,
@@ -743,31 +740,34 @@ private void findApiKeys(final BoolQueryBuilder boolQuery, boolean filterOutInva
expiredQuery.should(QueryBuilders.boolQuery().mustNot(QueryBuilders.existsQuery("expiration_time")));
boolQuery.filter(expiredQuery);
}
- final SearchRequest request = client.prepareSearch(SECURITY_MAIN_ALIAS)
- .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings))
- .setQuery(boolQuery)
- .setVersion(false)
- .setSize(1000)
- .setFetchSource(true)
- .request();
- securityIndex.checkIndexVersionThenExecute(listener::onFailure,
- () -> ScrollHelper.fetchAllByEntity(client, request, listener,
- (SearchHit hit) -> {
- Map source = hit.getSourceAsMap();
- String name = (String) source.get("name");
- String id = hit.getId();
- Long creation = (Long) source.get("creation_time");
- Long expiration = (Long) source.get("expiration_time");
- Boolean invalidated = (Boolean) source.get("api_key_invalidated");
- String username = (String) ((Map) source.get("creator")).get("principal");
- String realm = (String) ((Map) source.get("creator")).get("realm");
- return new ApiKey(name, id, Instant.ofEpochMilli(creation),
- (expiration != null) ? Instant.ofEpochMilli(expiration) : null, invalidated, username, realm);
- }));
- }
-
- private void findApiKeyForApiKeyName(String apiKeyName, boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys,
- ActionListener> listener) {
+ try (ThreadContext.StoredContext ignore = client.threadPool().getThreadContext().stashWithOrigin(SECURITY_ORIGIN)) {
+ final SearchRequest request = client.prepareSearch(SECURITY_MAIN_ALIAS)
+ .setScroll(DEFAULT_KEEPALIVE_SETTING.get(settings))
+ .setQuery(boolQuery)
+ .setVersion(false)
+ .setSize(1000)
+ .setFetchSource(true)
+ .request();
+ securityIndex.checkIndexVersionThenExecute(listener::onFailure,
+ () -> ScrollHelper.fetchAllByEntity(client, request, listener,
+ (SearchHit hit) -> {
+ Map source = hit.getSourceAsMap();
+ String name = (String) source.get("name");
+ String id = hit.getId();
+ Long creation = (Long) source.get("creation_time");
+ Long expiration = (Long) source.get("expiration_time");
+ Boolean invalidated = (Boolean) source.get("api_key_invalidated");
+ String username = (String) ((Map) source.get("creator")).get("principal");
+ String realm = (String) ((Map) source.get("creator")).get("realm");
+ return new ApiKey(name, id, Instant.ofEpochMilli(creation),
+ (expiration != null) ? Instant.ofEpochMilli(expiration) : null, invalidated, username, realm);
+ }));
+ }
+ }
+
+ private void findApiKeysForUserRealmApiKeyIdAndNameCombination(String userName, String realmName, String apiKeyName, String apiKeyId,
+ boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys,
+ ActionListener> listener) {
final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze();
if (frozenSecurityIndex.indexExists() == false) {
listener.onResponse(Collections.emptyList());
@@ -776,25 +776,18 @@ private void findApiKeyForApiKeyName(String apiKeyName, boolean filterOutInvalid
} else {
final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.filter(QueryBuilders.termQuery("doc_type", "api_key"));
+ if (Strings.hasText(userName)) {
+ boolQuery.filter(QueryBuilders.termQuery("creator.principal", userName));
+ }
+ if (Strings.hasText(realmName)) {
+ boolQuery.filter(QueryBuilders.termQuery("creator.realm", realmName));
+ }
if (Strings.hasText(apiKeyName)) {
boolQuery.filter(QueryBuilders.termQuery("name", apiKeyName));
}
-
- findApiKeys(boolQuery, filterOutInvalidatedKeys, filterOutExpiredKeys, listener);
- }
- }
-
- private void findApiKeysForApiKeyId(String apiKeyId, boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys,
- ActionListener> listener) {
- final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze();
- if (frozenSecurityIndex.indexExists() == false) {
- listener.onResponse(Collections.emptyList());
- } else if (frozenSecurityIndex.isAvailable() == false) {
- listener.onFailure(frozenSecurityIndex.getUnavailableReason());
- } else {
- final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
- .filter(QueryBuilders.termQuery("doc_type", "api_key"))
- .filter(QueryBuilders.termQuery("_id", apiKeyId));
+ if (Strings.hasText(apiKeyId)) {
+ boolQuery.filter(QueryBuilders.termQuery("_id", apiKeyId));
+ }
findApiKeys(boolQuery, filterOutInvalidatedKeys, filterOutExpiredKeys, listener);
}
@@ -923,70 +916,80 @@ private void maybeStartApiKeyRemover() {
}
/**
- * Get API keys for given realm and user name.
- * @param realmName realm name
- * @param userName user name
+ * Get API key(s) owned by the authenticateed user.
+ * If no API key id or name is specified in the request then returns all API keys for current
+ * logged-in user.
+ *
+ * @param getMyApiKeyRequest {@link GetMyApiKeyRequest}
* @param listener listener for {@link GetApiKeyResponse}
*/
- public void getApiKeysForRealmAndUser(String realmName, String userName, ActionListener listener) {
+ public void getApiKeysForCurrentUser(final GetMyApiKeyRequest getMyApiKeyRequest, ActionListener listener) {
ensureEnabled();
- if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false) {
- logger.trace("No realm name or username provided");
- listener.onFailure(new IllegalArgumentException("realm name or username must be provided"));
+
+ final Authentication authentication = Authentication.getAuthentication(threadPool.getThreadContext());
+ final String userName;
+ final String realmName;
+ final String apiKeyId;
+ final String apiKeyName;
+ if (authentication.getAuthenticatedBy().getType().equals("_es_api_key")) {
+ // in case of authenticated by API key, fetch key by API key id from authentication metadata
+ realmName = null;
+ userName = null;
+ apiKeyId = (String) authentication.getMetadata().get(API_KEY_ID_KEY);
+ apiKeyName = null;
} else {
- findApiKeysForUserAndRealm(userName, realmName, false, false, ActionListener.wrap(apiKeyInfos -> {
- if (apiKeyInfos.isEmpty()) {
- logger.warn("No active api keys found for realm [{}] and username [{}]", realmName, userName);
- listener.onResponse(GetApiKeyResponse.emptyResponse());
- } else {
- listener.onResponse(new GetApiKeyResponse(apiKeyInfos));
- }
- }, listener::onFailure));
+ realmName = authentication.getLookedUpBy() == null ? authentication.getAuthenticatedBy().getName()
+ : authentication.getLookedUpBy().getName();
+ userName = authentication.getUser().principal();
+ apiKeyId = getMyApiKeyRequest.getApiKeyId();
+ apiKeyName = getMyApiKeyRequest.getApiKeyName();
}
- }
- /**
- * Get API key for given API key id
- * @param apiKeyId API key id
- * @param listener listener for {@link GetApiKeyResponse}
- */
- public void getApiKeyForApiKeyId(String apiKeyId, ActionListener listener) {
- ensureEnabled();
- if (Strings.hasText(apiKeyId) == false) {
- logger.trace("No api key id provided");
- listener.onFailure(new IllegalArgumentException("api key id must be provided"));
- } else {
- findApiKeysForApiKeyId(apiKeyId, false, false, ActionListener.wrap(apiKeyInfos -> {
+ findApiKeysForUserRealmApiKeyIdAndNameCombination(userName, realmName, apiKeyName, apiKeyId, false, false,
+ ActionListener.wrap(apiKeyInfos -> {
if (apiKeyInfos.isEmpty()) {
- logger.warn("No api key found for api key id [{}]", apiKeyId);
+ logger.warn("No active api keys found for {}", getMyApiKeyRequest);
listener.onResponse(GetApiKeyResponse.emptyResponse());
} else {
listener.onResponse(new GetApiKeyResponse(apiKeyInfos));
}
}, listener::onFailure));
- }
}
/**
- * Get API key for given API key name
- * @param apiKeyName API key name
+ * Get API keys.
+ * @param getApiKeyRequest {@link GetApiKeyRequest}
* @param listener listener for {@link GetApiKeyResponse}
*/
- public void getApiKeyForApiKeyName(String apiKeyName, ActionListener listener) {
+ public void getApiKeys(final GetApiKeyRequest getApiKeyRequest, ActionListener listener) {
ensureEnabled();
- if (Strings.hasText(apiKeyName) == false) {
- logger.trace("No api key name provided");
- listener.onFailure(new IllegalArgumentException("api key name must be provided"));
- } else {
- findApiKeyForApiKeyName(apiKeyName, false, false, ActionListener.wrap(apiKeyInfos -> {
+ final String realmName = getApiKeyRequest.getRealmName();
+ final String userName = getApiKeyRequest.getUserName();
+ final String apiKeyId = getApiKeyRequest.getApiKeyId();
+ final String apiKeyName = getApiKeyRequest.getApiKeyName();
+
+ if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(apiKeyId) == false
+ && Strings.hasText(apiKeyName) == false) {
+ listener.onFailure(new IllegalArgumentException("one of [api key id, api key name, username, realm name] must be specified"));
+ }
+ if (Strings.hasText(apiKeyId) || Strings.hasText(apiKeyName)) {
+ if (Strings.hasText(realmName) || Strings.hasText(userName)) {
+ listener.onFailure(new IllegalArgumentException(
+ "username or realm name must not be specified when the api key id or api key name is specified"));
+ }
+ }
+ if (Strings.hasText(apiKeyId) && Strings.hasText(apiKeyName)) {
+ listener.onFailure(new IllegalArgumentException("only one of [api key id, api key name] can be specified"));
+ }
+ findApiKeysForUserRealmApiKeyIdAndNameCombination(userName, realmName,
+ apiKeyName, apiKeyId, false, false, ActionListener.wrap(apiKeyInfos -> {
if (apiKeyInfos.isEmpty()) {
- logger.warn("No api key found for api key name [{}]", apiKeyName);
+ logger.warn("No active api keys found for {}", getApiKeyRequest);
listener.onResponse(GetApiKeyResponse.emptyResponse());
} else {
listener.onResponse(new GetApiKeyResponse(apiKeyInfos));
}
}, listener::onFailure));
- }
}
final class CachedApiKeyHashResult {
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java
index e2824e74ecafe..b977148b4c396 100644
--- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/RBACEngine.java
@@ -33,6 +33,8 @@
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.transport.TransportActionProxy;
import org.elasticsearch.transport.TransportRequest;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateAction;
import org.elasticsearch.xpack.core.security.action.user.ChangePasswordAction;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesAction;
@@ -85,7 +87,7 @@
public class RBACEngine implements AuthorizationEngine {
private static final Predicate SAME_USER_PRIVILEGE = Automatons.predicate(
- ChangePasswordAction.NAME, AuthenticateAction.NAME, HasPrivilegesAction.NAME, GetUserPrivilegesAction.NAME);
+ ChangePasswordAction.NAME, AuthenticateAction.NAME, HasPrivilegesAction.NAME, GetUserPrivilegesAction.NAME, GetMyApiKeyAction.NAME);
private static final String INDEX_SUB_REQUEST_PRIMARY = IndexAction.NAME + "[p]";
private static final String INDEX_SUB_REQUEST_REPLICA = IndexAction.NAME + "[r]";
private static final String DELETE_SUB_REQUEST_PRIMARY = DeleteAction.NAME + "[p]";
@@ -153,26 +155,31 @@ public void authorizeClusterAction(RequestInfo requestInfo, AuthorizationInfo au
boolean checkSameUserPermissions(String action, TransportRequest request, Authentication authentication) {
final boolean actionAllowed = SAME_USER_PRIVILEGE.test(action);
if (actionAllowed) {
- if (request instanceof UserRequest == false) {
- assert false : "right now only a user request should be allowed";
- return false;
- }
- UserRequest userRequest = (UserRequest) request;
- String[] usernames = userRequest.usernames();
- if (usernames == null || usernames.length != 1 || usernames[0] == null) {
- assert false : "this role should only be used for actions to apply to a single user";
+ if (request instanceof UserRequest) {
+ UserRequest userRequest = (UserRequest) request;
+ String[] usernames = userRequest.usernames();
+ if (usernames == null || usernames.length != 1 || usernames[0] == null) {
+ assert false : "this role should only be used for actions to apply to a single user";
+ return false;
+ }
+ final String username = usernames[0];
+ final boolean sameUsername = authentication.getUser().principal().equals(username);
+ if (sameUsername && ChangePasswordAction.NAME.equals(action)) {
+ return checkChangePasswordAction(authentication);
+ }
+
+ assert AuthenticateAction.NAME.equals(action) || HasPrivilegesAction.NAME.equals(action)
+ || GetUserPrivilegesAction.NAME.equals(action)
+ || sameUsername == false : "Action '" + action + "' should not be possible when sameUsername=" + sameUsername;
+ return sameUsername;
+ } else if (request instanceof GetMyApiKeyRequest) {
+ if (authentication.getAuthenticatedBy().getType().equals("_es_api_key")) {
+ return true;
+ }
+ } else {
+ assert false : "only a user request or get my API key request should be allowed";
return false;
}
- final String username = usernames[0];
- final boolean sameUsername = authentication.getUser().principal().equals(username);
- if (sameUsername && ChangePasswordAction.NAME.equals(action)) {
- return checkChangePasswordAction(authentication);
- }
-
- assert AuthenticateAction.NAME.equals(action) || HasPrivilegesAction.NAME.equals(action)
- || GetUserPrivilegesAction.NAME.equals(action) || sameUsername == false
- : "Action '" + action + "' should not be possible when sameUsername=" + sameUsername;
- return sameUsername;
}
return false;
}
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestCreateApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestCreateApiKeyAction.java
index 14d4726553dff..84bb746808197 100644
--- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestCreateApiKeyAction.java
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestCreateApiKeyAction.java
@@ -39,7 +39,7 @@ public RestCreateApiKeyAction(Settings settings, RestController controller, XPac
@Override
public String getName() {
- return "xpack_security_create_api_key";
+ return "security_create_api_key";
}
@Override
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyAction.java
index 71ed5a06efb65..9430124175b94 100644
--- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyAction.java
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyAction.java
@@ -57,7 +57,7 @@ public RestResponse buildResponse(GetApiKeyResponse getApiKeyResponse, XContentB
@Override
public String getName() {
- return "xpack_security_get_api_key";
+ return "security_get_api_key";
}
}
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetMyApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetMyApiKeyAction.java
new file mode 100644
index 0000000000000..d562e9b893f8e
--- /dev/null
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetMyApiKeyAction.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.security.rest.action.apikey;
+
+import org.elasticsearch.client.node.NodeClient;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.license.XPackLicenseState;
+import org.elasticsearch.rest.BytesRestResponse;
+import org.elasticsearch.rest.RestController;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.RestResponse;
+import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.rest.action.RestBuilderListener;
+import org.elasticsearch.xpack.core.security.action.GetApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyRequest;
+import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
+
+import java.io.IOException;
+
+/**
+ * Rest action to get information for one or more API keys owned by the authenticated user.
+ */
+public class RestGetMyApiKeyAction extends SecurityBaseRestHandler {
+
+ public RestGetMyApiKeyAction(Settings settings, RestController controller, XPackLicenseState licenseState) {
+ super(settings, licenseState);
+ controller.registerHandler(RestRequest.Method.GET, "/_security/api_key/my", this);
+ }
+
+ @Override
+ protected RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
+ final String apiKeyId = request.param("id");
+ final String apiKeyName = request.param("name");
+ final GetMyApiKeyRequest getApiKeyRequest = new GetMyApiKeyRequest(apiKeyId, apiKeyName);
+ return channel -> client.execute(GetMyApiKeyAction.INSTANCE, getApiKeyRequest,
+ new RestBuilderListener(channel) {
+ @Override
+ public RestResponse buildResponse(GetApiKeyResponse getApiKeyResponse, XContentBuilder builder) throws Exception {
+ getApiKeyResponse.toXContent(builder, channel.request());
+
+ // return HTTP status 404 if no API key found for API key id
+ if (Strings.hasText(apiKeyId) && getApiKeyResponse.getApiKeyInfos().length == 0) {
+ return new BytesRestResponse(RestStatus.NOT_FOUND, builder);
+ }
+ return new BytesRestResponse(RestStatus.OK, builder);
+ }
+ });
+ }
+
+ @Override
+ public String getName() {
+ return "security_get_my_api_key";
+ }
+}
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyAction.java
index b11a0edde42f8..4675e3dac65d7 100644
--- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyAction.java
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyAction.java
@@ -64,7 +64,7 @@ public RestResponse buildResponse(InvalidateApiKeyResponse invalidateResp,
@Override
public String getName() {
- return "xpack_security_invalidate_api_key";
+ return "security_invalidate_api_key";
}
}
diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateMyApiKeyAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateMyApiKeyAction.java
new file mode 100644
index 0000000000000..a4ab483ea6295
--- /dev/null
+++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateMyApiKeyAction.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+package org.elasticsearch.xpack.security.rest.action.apikey;
+
+import org.elasticsearch.client.node.NodeClient;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.license.XPackLicenseState;
+import org.elasticsearch.rest.BytesRestResponse;
+import org.elasticsearch.rest.RestController;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.RestResponse;
+import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.rest.action.RestBuilderListener;
+import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.InvalidateMyApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.InvalidateMyApiKeyRequest;
+import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
+
+import java.io.IOException;
+
+/**
+ * Rest action to invalidate one or more API keys owned by the authenticated user.
+ */
+public final class RestInvalidateMyApiKeyAction extends SecurityBaseRestHandler {
+ static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("invalidate_my_api_key",
+ a -> {
+ return new InvalidateMyApiKeyRequest((String) a[0], (String) a[1]);
+ });
+
+ static {
+ PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("id"));
+ PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("name"));
+ }
+
+ public RestInvalidateMyApiKeyAction(Settings settings, RestController controller, XPackLicenseState licenseState) {
+ super(settings, licenseState);
+ controller.registerHandler(RestRequest.Method.DELETE, "/_security/api_key/my", this);
+ }
+
+ @Override
+ protected RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
+ try (XContentParser parser = request.contentParser()) {
+ final InvalidateMyApiKeyRequest invalidateApiKeyRequest = PARSER.parse(parser, null);
+ return channel -> client.execute(InvalidateMyApiKeyAction.INSTANCE, invalidateApiKeyRequest,
+ new RestBuilderListener(channel) {
+ @Override
+ public RestResponse buildResponse(InvalidateApiKeyResponse invalidateResp,
+ XContentBuilder builder) throws Exception {
+ invalidateResp.toXContent(builder, channel.request());
+ return new BytesRestResponse(RestStatus.OK, builder);
+ }
+ });
+ }
+ }
+
+ @Override
+ public String getName() {
+ return "security_invalidate_my_api_key";
+ }
+
+}
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 f6849cae4c1cd..62b6cbbe6473f 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
@@ -29,8 +29,10 @@
import org.elasticsearch.xpack.core.security.action.CreateApiKeyResponse;
import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.GetApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse;
+import org.elasticsearch.xpack.core.security.action.InvalidateMyApiKeyRequest;
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.client.SecurityClient;
@@ -60,11 +62,49 @@
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.isIn;
import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
public class ApiKeyIntegTests extends SecurityIntegTestCase {
private static final long DELETE_INTERVAL_MILLIS = 100L;
+
+ @Override
+ public String configRoles() {
+ return super.configRoles() + "\n" +
+ "manage_api_key_role:\n" +
+ " cluster: [\"manage_api_key\"]\n" +
+ "create_api_key_role:\n" +
+ " cluster: [\"create_api_key\"]\n" +
+ "owner_manage_api_key_role:\n" +
+ " cluster: [\"owner_manage_api_key\"]\n" +
+ "no_manage_api_key_role:\n" +
+ " indices:\n" +
+ " - names: '*'\n" +
+ " privileges:\n" +
+ " - all\n";
+ }
+
+ @Override
+ public String configUsers() {
+ final String usersPasswdHashed = new String(
+ getFastStoredHashAlgoForTests().hash(SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING));
+ return super.configUsers() +
+ "user_with_manage_api_key_role:" + usersPasswdHashed + "\n" +
+ "user_with_create_api_key_role:" + usersPasswdHashed + "\n" +
+ "user_with_owner_manage_api_key_role:" + usersPasswdHashed + "\n" +
+ "user_with_no_manage_api_key_role:" + usersPasswdHashed + "\n";
+ }
+
+ @Override
+ public String configUsersRoles() {
+ return super.configUsersRoles() +
+ "manage_api_key_role:user_with_manage_api_key_role\n" +
+ "create_api_key_role:user_with_create_api_key_role\n" +
+ "owner_manage_api_key_role:user_with_owner_manage_api_key_role\n" +
+ "no_manage_api_key_role:user_with_no_manage_api_key_role";
+ }
+
@Override
public Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
@@ -144,6 +184,38 @@ public void testCreateApiKey() {
assertThat(e.status(), is(RestStatus.FORBIDDEN));
}
+ public void testApiKeyWithMinimalRoleCanGetApiKeyInformation() {
+ final RoleDescriptor descriptor = new RoleDescriptor("role", new String[] { "monitor" }, null, null);
+ Client client = client().filterWithHeader(Collections.singletonMap("Authorization",
+ UsernamePasswordToken.basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER,
+ SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
+ SecurityClient securityClient = new SecurityClient(client);
+ final CreateApiKeyResponse response = securityClient.prepareCreateApiKey()
+ .setName("test key")
+ .setExpiration(TimeValue.timeValueHours(TimeUnit.DAYS.toHours(7L)))
+ .setRoleDescriptors(Collections.singletonList(descriptor))
+ .get();
+
+ assertEquals("test key", response.getName());
+ assertNotNull(response.getId());
+ assertNotNull(response.getKey());
+
+ // use the first ApiKey for authorized action
+ final String base64ApiKeyKeyValue = Base64.getEncoder().encodeToString(
+ (response.getId() + ":" + response.getKey().toString()).getBytes(StandardCharsets.UTF_8));
+ client = client().filterWithHeader(Collections.singletonMap("Authorization", "ApiKey " + base64ApiKeyKeyValue));
+ securityClient = new SecurityClient(client);
+ PlainActionFuture listener = new PlainActionFuture<>();
+ GetMyApiKeyRequest request = randomFrom(GetMyApiKeyRequest.usingApiKeyId(response.getId()),
+ GetMyApiKeyRequest.usingApiKeyName(response.getName()), new GetMyApiKeyRequest());
+ securityClient.getMyApiKey(request, listener);
+ GetApiKeyResponse apiKeyResponse = listener.actionGet();
+ assertThat(apiKeyResponse.getApiKeyInfos().length, is(1));
+ assertThat(apiKeyResponse.getApiKeyInfos()[0].getId(), is(response.getId()));
+ assertThat(apiKeyResponse.getApiKeyInfos()[0].getName(), is(response.getName()));
+ assertThat(apiKeyResponse.getApiKeyInfos()[0].getExpiration(), is(response.getExpiration()));
+ }
+
public void testCreateApiKeyFailsWhenApiKeyWithSameNameAlreadyExists() throws InterruptedException, ExecutionException {
String keyName = randomAlphaOfLength(5);
List responses = new ArrayList<>();
@@ -479,6 +551,143 @@ public void testGetApiKeysForApiKeyId() throws InterruptedException, ExecutionEx
verifyGetResponse(1, responses, response, Collections.singleton(responses.get(0).getId()), null);
}
+ public void testCreateApiKeyAuthorization() {
+ // user_with_manage_api_key_role should be able to create API key
+ List responses = createApiKeys("user_with_manage_api_key_role", 1, null);
+ assertThat(responses.get(0).getKey(), is(notNullValue()));
+
+ // user_with_create_api_key_role should be able to create API key
+ responses = createApiKeys("user_with_create_api_key_role", 1, null);
+ assertThat(responses.get(0).getKey(), is(notNullValue()));
+
+ // user_with_no_manage_api_key_role should not be able to create API key
+ Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken
+ .basicAuthHeaderValue("user_with_no_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
+ SecurityClient securityClient = new SecurityClient(client);
+ RoleDescriptor roleDescriptor = new RoleDescriptor("role", new String[] { "monitor" }, null, null);
+ ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class,
+ () -> securityClient.prepareCreateApiKey()
+ .setName("test-key-" + randomAlphaOfLengthBetween(5, 9))
+ .setRoleDescriptors(Collections.singletonList(roleDescriptor))
+ .get());
+ assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/create", "user_with_no_manage_api_key_role");
+ }
+
+ public void testGetApiKeyAuthorization() throws InterruptedException, ExecutionException {
+ List userWithManageApiKeyRoleApiKeys = createApiKeys("user_with_manage_api_key_role", 2, null);
+ List userWithOwnerManageApiKeyRoleApiKeys = createApiKeys("user_with_owner_manage_api_key_role", 2, null);
+ List userWithCreateApiKeyRoleApiKeys = createApiKeys("user_with_create_api_key_role", 1, null);
+
+ // user_with_manage_api_key_role should be able to get any user's API Key
+ {
+ final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken
+ .basicAuthHeaderValue("user_with_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
+ final SecurityClient securityClient = new SecurityClient(client);
+ PlainActionFuture listener = new PlainActionFuture<>();
+ securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(userWithManageApiKeyRoleApiKeys.get(0).getId()), listener);
+ GetApiKeyResponse response = listener.actionGet();
+ assertThat(response.getApiKeyInfos().length, is(1));
+ assertThat(response.getApiKeyInfos()[0].getId(), is(userWithManageApiKeyRoleApiKeys.get(0).getId()));
+
+ listener = new PlainActionFuture<>();
+ securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(userWithOwnerManageApiKeyRoleApiKeys.get(0).getId()), listener);
+ response = listener.actionGet();
+ assertThat(response.getApiKeyInfos().length, is(1));
+ assertThat(response.getApiKeyInfos()[0].getId(), is(userWithOwnerManageApiKeyRoleApiKeys.get(0).getId()));
+ }
+
+ // user_with_owner_manage_api_key_role or user_with_create_api_key_role should be able to get its own API key but not any other
+ // user's API key
+ {
+ final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken
+ .basicAuthHeaderValue("user_with_owner_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
+ final SecurityClient securityClient = new SecurityClient(client);
+ PlainActionFuture listener = new PlainActionFuture<>();
+ securityClient.getMyApiKey(new GetMyApiKeyRequest(), listener);
+ GetApiKeyResponse response = listener.actionGet();
+ assertThat(response.getApiKeyInfos().length, is(2));
+
+ final PlainActionFuture getApiKeyOfOtherUserListener = new PlainActionFuture<>();
+ securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(userWithManageApiKeyRoleApiKeys.get(0).getId()),
+ getApiKeyOfOtherUserListener);
+ final ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class,
+ () -> getApiKeyOfOtherUserListener.actionGet());
+ assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", "user_with_owner_manage_api_key_role");
+ }
+
+ // user_with_create_api_key_role should be allowed to get it's own API key but not any other user's API key
+ {
+ final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken
+ .basicAuthHeaderValue("user_with_create_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
+ final SecurityClient securityClient = new SecurityClient(client);
+ PlainActionFuture listener = new PlainActionFuture<>();
+ securityClient.getMyApiKey(GetMyApiKeyRequest.usingApiKeyName(userWithCreateApiKeyRoleApiKeys.get(0).getName()), listener);
+ GetApiKeyResponse response = listener.actionGet();
+ assertThat(response.getApiKeyInfos().length, is(1));
+ assertThat(response.getApiKeyInfos()[0].getId(), is(userWithCreateApiKeyRoleApiKeys.get(0).getId()));
+
+ final PlainActionFuture getApiKeyOfOtherUserListener = new PlainActionFuture<>();
+ securityClient.getApiKey(GetApiKeyRequest.usingApiKeyId(userWithManageApiKeyRoleApiKeys.get(0).getId()),
+ getApiKeyOfOtherUserListener);
+ final ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class,
+ () -> getApiKeyOfOtherUserListener.actionGet());
+ assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", "user_with_create_api_key_role");
+ }
+ }
+
+ public void testInvalidateApiKeyAuthorization() throws InterruptedException, ExecutionException {
+ List userWithManageApiKeyRoleApiKeys = createApiKeys("user_with_manage_api_key_role", 2, null);
+ List userWithOwnerManageApiKeyRoleApiKeys = createApiKeys("user_with_owner_manage_api_key_role", 2, null);
+ List userWithCreateApiKeyRoleApiKeys = createApiKeys("user_with_create_api_key_role", 1, null);
+
+ // user_with_manage_api_key_role should be able to invalidate any user's API Key
+ InvalidateApiKeyResponse invalidateApiKeyResponse = invalidateApiKey("user_with_manage_api_key_role", null, null,
+ userWithManageApiKeyRoleApiKeys.get(0).getName(), null);
+ verifyInvalidateResponse(1, Collections.singletonList(userWithManageApiKeyRoleApiKeys.get(0)), invalidateApiKeyResponse);
+ invalidateApiKeyResponse = invalidateApiKey("user_with_manage_api_key_role", null, null,
+ userWithOwnerManageApiKeyRoleApiKeys.get(0).getName(), null);
+ verifyInvalidateResponse(1, Collections.singletonList(userWithOwnerManageApiKeyRoleApiKeys.get(0)), invalidateApiKeyResponse);
+
+ // user_with_owner_manage_api_key_role should be able to invalidate its own API key but not any other user's API key
+ {
+ final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken
+ .basicAuthHeaderValue("user_with_owner_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
+ final SecurityClient securityClient = new SecurityClient(client);
+ final PlainActionFuture listener = new PlainActionFuture<>();
+ securityClient.invalidateMyApiKey(
+ InvalidateMyApiKeyRequest.usingApiKeyName(userWithOwnerManageApiKeyRoleApiKeys.get(1).getName()), listener);
+ invalidateApiKeyResponse = listener.actionGet();
+ verifyInvalidateResponse(1, Collections.singletonList(userWithOwnerManageApiKeyRoleApiKeys.get(1)), invalidateApiKeyResponse);
+
+ final ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class,
+ () -> invalidateApiKey("user_with_owner_manage_api_key_role", null, null,
+ userWithManageApiKeyRoleApiKeys.get(1).getName(), null));
+ assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", "user_with_owner_manage_api_key_role");
+ }
+
+ // user_with_create_api_key_role should not be allowed to invalidate it's own API keys or any other users API keys
+ {
+ final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken
+ .basicAuthHeaderValue("user_with_create_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
+ final SecurityClient securityClient = new SecurityClient(client);
+
+ ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class,
+ () -> invalidateApiKey("user_with_create_api_key_role", null, null, userWithManageApiKeyRoleApiKeys.get(1).getName(),
+ null));
+ assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", "user_with_create_api_key_role");
+
+ final PlainActionFuture listener = new PlainActionFuture<>();
+ securityClient.invalidateMyApiKey(InvalidateMyApiKeyRequest.usingApiKeyName(userWithCreateApiKeyRoleApiKeys.get(0).getName()),
+ listener);
+ ese = expectThrows(ElasticsearchSecurityException.class, () -> listener.actionGet());
+ assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate/my", "user_with_create_api_key_role");
+ }
+ }
+
+ private void assertErrorMessage(final ElasticsearchSecurityException ese, String action, String userName) {
+ assertThat(ese.getMessage(), is("action [" + action + "] is unauthorized for user [" + userName + "]"));
+ }
+
public void testGetApiKeysForApiKeyName() throws InterruptedException, ExecutionException {
List responses = createApiKeys(1, null);
Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken
@@ -517,12 +726,12 @@ private void verifyGetResponse(int noOfApiKeys, List respo
}
- private List createApiKeys(int noOfApiKeys, TimeValue expiration) {
+ private List createApiKeys(String user, int noOfApiKeys, TimeValue expiration) {
List responses = new ArrayList<>();
for (int i = 0; i < noOfApiKeys; i++) {
final RoleDescriptor descriptor = new RoleDescriptor("role", new String[] { "monitor" }, null, null);
Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken
- .basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
+ .basicAuthHeaderValue(user, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
SecurityClient securityClient = new SecurityClient(client);
final CreateApiKeyResponse response = securityClient.prepareCreateApiKey()
.setName("test-key-" + randomAlphaOfLengthBetween(5, 9) + i).setExpiration(expiration)
@@ -534,4 +743,28 @@ private List createApiKeys(int noOfApiKeys, TimeValue expi
assertThat(responses.size(), is(noOfApiKeys));
return responses;
}
+
+ private List createApiKeys(int noOfApiKeys, TimeValue expiration) {
+ return createApiKeys(SecuritySettingsSource.TEST_SUPERUSER, noOfApiKeys, expiration);
+ }
+
+ private InvalidateApiKeyResponse invalidateApiKey(String executeActionAsUser, String realmName, String userName, String apiKeyName,
+ String apiKeyId) {
+ Client client = client().filterWithHeader(Collections.singletonMap("Authorization",
+ UsernamePasswordToken.basicAuthHeaderValue(executeActionAsUser, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING)));
+ SecurityClient securityClient = new SecurityClient(client);
+ PlainActionFuture listener = new PlainActionFuture<>();
+ if (Strings.hasText(realmName) && Strings.hasText(userName)) {
+ securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingRealmAndUserName(realmName, userName), listener);
+ } else if (Strings.hasText(realmName)) {
+ securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingRealmName(realmName), listener);
+ } else if (Strings.hasText(userName)) {
+ securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingUserName(userName), listener);
+ } else if (Strings.hasText(apiKeyName)) {
+ securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyName(apiKeyName), listener);
+ } else if (Strings.hasText(apiKeyId)) {
+ securityClient.invalidateApiKey(InvalidateApiKeyRequest.usingApiKeyId(apiKeyId), listener);
+ }
+ return listener.actionGet();
+ }
}
diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java
index 5c2e964c743c6..6139985963fcf 100644
--- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java
+++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/RBACEngineTests.java
@@ -21,6 +21,10 @@
import org.elasticsearch.license.GetLicenseAction;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.transport.TransportRequest;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.GetMyApiKeyRequest;
+import org.elasticsearch.xpack.core.security.action.InvalidateMyApiKeyAction;
+import org.elasticsearch.xpack.core.security.action.InvalidateMyApiKeyRequest;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateAction;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateRequest;
import org.elasticsearch.xpack.core.security.action.user.AuthenticateRequestBuilder;
@@ -110,6 +114,21 @@ public void testSameUserPermission() {
assertTrue(engine.checkSameUserPermissions(action, request, authentication));
}
+ public void testSamUserPermissionForGetMyApiKeyAction() {
+ final User user = new User("api-key-principal");
+ final Authentication authentication = mock(Authentication.class);
+ final Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class);
+ when(authentication.getUser()).thenReturn(user);
+ when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy);
+ when(authenticatedBy.getType()).thenReturn("_es_api_key");
+
+ TransportRequest request = new GetMyApiKeyRequest(null, null);
+ assertTrue(engine.checkSameUserPermissions(GetMyApiKeyAction.NAME, request, authentication));
+
+ request = new InvalidateMyApiKeyRequest();
+ assertFalse(engine.checkSameUserPermissions(InvalidateMyApiKeyAction.NAME, request, authentication));
+ }
+
public void testSameUserPermissionDoesNotAllowNonMatchingUsername() {
final User authUser = new User("admin", new String[]{"bar"});
final User user = new User("joe", null, authUser);
diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/api/security.get_my_api_key.json b/x-pack/plugin/src/test/resources/rest-api-spec/api/security.get_my_api_key.json
new file mode 100644
index 0000000000000..6f5b40fb97ce8
--- /dev/null
+++ b/x-pack/plugin/src/test/resources/rest-api-spec/api/security.get_my_api_key.json
@@ -0,0 +1,22 @@
+{
+ "security.get_my_api_key": {
+ "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-my-api-key.html",
+ "methods": [ "GET" ],
+ "url": {
+ "path": "/_security/api_key/my",
+ "paths": [ "/_security/api_key/my" ],
+ "parts": {},
+ "params": {
+ "id": {
+ "type": "string",
+ "description": "API key id of the API key to be retrieved"
+ },
+ "name": {
+ "type": "string",
+ "description": "API key name of the API key to be retrieved"
+ }
+ }
+ },
+ "body": null
+ }
+}
diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/api/security.invalidate_my_api_key.json b/x-pack/plugin/src/test/resources/rest-api-spec/api/security.invalidate_my_api_key.json
new file mode 100644
index 0000000000000..bbbc43cb9929b
--- /dev/null
+++ b/x-pack/plugin/src/test/resources/rest-api-spec/api/security.invalidate_my_api_key.json
@@ -0,0 +1,15 @@
+{
+ "security.invalidate_my_api_key": {
+ "documentation": "https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-invalidate-my-api-key.html",
+ "methods": [ "DELETE" ],
+ "url": {
+ "path": "/_security/api_key/my",
+ "paths": [ "/_security/api_key/my" ],
+ "parts": {}
+ },
+ "body": {
+ "description" : "The api key request to invalidate API key(s) owned by authenticated user",
+ "required" : true
+ }
+ }
+}