Skip to content

Commit 7625930

Browse files
author
Yogesh Gaikwad
committed
Add granular API key privileges
In the current implementation of API keys, to create/get/invalidate API keys one needs to be super user which limits the usage of API keys. We would want to have fine grained privileges rather than system wide privileges for using API keys. This commit adds: - `manage_api_key` cluster privilege which allows users to create, retrieve and invalidate **_any_** API keys in the system. This allows for limited access than `manage_security` or `all`. - `owner_manage_api_key` cluster privilege which allows user to create, retrieve and invalidate API keys owned by this user only. - `create_api_key` is a sub privilege which allows for user to create but not invalidate API keys. - an API key with no api key manage privilege can retrieve its own information Also introduces following rest APIs to manage owned API keys for a user: GET /_security/api_key/my DELETE /_security/api_key/my
1 parent 29fefcf commit 7625930

File tree

38 files changed

+1822
-205
lines changed

38 files changed

+1822
-205
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.elasticsearch.client.security.EnableUserRequest;
4444
import org.elasticsearch.client.security.GetApiKeyRequest;
4545
import org.elasticsearch.client.security.GetApiKeyResponse;
46+
import org.elasticsearch.client.security.GetMyApiKeyRequest;
4647
import org.elasticsearch.client.security.GetPrivilegesRequest;
4748
import org.elasticsearch.client.security.GetPrivilegesResponse;
4849
import org.elasticsearch.client.security.GetRoleMappingsRequest;
@@ -59,6 +60,7 @@
5960
import org.elasticsearch.client.security.HasPrivilegesResponse;
6061
import org.elasticsearch.client.security.InvalidateApiKeyRequest;
6162
import org.elasticsearch.client.security.InvalidateApiKeyResponse;
63+
import org.elasticsearch.client.security.InvalidateMyApiKeyRequest;
6264
import org.elasticsearch.client.security.InvalidateTokenRequest;
6365
import org.elasticsearch.client.security.InvalidateTokenResponse;
6466
import org.elasticsearch.client.security.PutPrivilegesRequest;
@@ -909,6 +911,36 @@ public void getApiKeyAsync(final GetApiKeyRequest request, final RequestOptions
909911
GetApiKeyResponse::fromXContent, listener, emptySet());
910912
}
911913

914+
/**
915+
* Retrieve information for API key(s) owned by authenticated user.<br>
916+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-my-api-key.html">
917+
* the docs</a> for more.
918+
*
919+
* @param request the request to retrieve API key(s)
920+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
921+
* @return the response from the create API key call
922+
* @throws IOException in case there is a problem sending the request or parsing back the response
923+
*/
924+
public GetApiKeyResponse getMyApiKey(final GetMyApiKeyRequest request, final RequestOptions options) throws IOException {
925+
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::getMyApiKey, options,
926+
GetApiKeyResponse::fromXContent, emptySet());
927+
}
928+
929+
/**
930+
* Asynchronously retrieve information for API key(s) owned by authenticated user.<br>
931+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-my-api-key.html">
932+
* the docs</a> for more.
933+
*
934+
* @param request the request to retrieve API key(s)
935+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
936+
* @param listener the listener to be notified upon request completion
937+
*/
938+
public void getMyApiKeyAsync(final GetMyApiKeyRequest request, final RequestOptions options,
939+
final ActionListener<GetApiKeyResponse> listener) {
940+
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::getMyApiKey, options,
941+
GetApiKeyResponse::fromXContent, listener, emptySet());
942+
}
943+
912944
/**
913945
* Invalidate API Key(s).<br>
914946
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-invalidate-api-key.html">
@@ -939,4 +971,35 @@ public void invalidateApiKeyAsync(final InvalidateApiKeyRequest request, final R
939971
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::invalidateApiKey, options,
940972
InvalidateApiKeyResponse::fromXContent, listener, emptySet());
941973
}
974+
975+
/**
976+
* Invalidate API key(s) owned by authenticated user.<br>
977+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-invalidate-my-api-key.html">
978+
* the docs</a> for more.
979+
*
980+
* @param request the request to invalidate API key(s)
981+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
982+
* @return the response from the invalidate API key call
983+
* @throws IOException in case there is a problem sending the request or parsing back the response
984+
*/
985+
public InvalidateApiKeyResponse invalidateMyApiKey(final InvalidateMyApiKeyRequest request, final RequestOptions options)
986+
throws IOException {
987+
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::invalidateMyApiKey, options,
988+
InvalidateApiKeyResponse::fromXContent, emptySet());
989+
}
990+
991+
/**
992+
* Asynchronously invalidates API key(s) owned by authenticated user.<br>
993+
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-invalidate-my-api-key.html">
994+
* the docs</a> for more.
995+
*
996+
* @param request the request to invalidate API key(s)
997+
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
998+
* @param listener the listener to be notified upon request completion
999+
*/
1000+
public void invalidateMyApiKeyAsync(final InvalidateMyApiKeyRequest request, final RequestOptions options,
1001+
final ActionListener<InvalidateApiKeyResponse> listener) {
1002+
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::invalidateMyApiKey, options,
1003+
InvalidateApiKeyResponse::fromXContent, listener, emptySet());
1004+
}
9421005
}

client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@
3535
import org.elasticsearch.client.security.DisableUserRequest;
3636
import org.elasticsearch.client.security.EnableUserRequest;
3737
import org.elasticsearch.client.security.GetApiKeyRequest;
38+
import org.elasticsearch.client.security.GetMyApiKeyRequest;
3839
import org.elasticsearch.client.security.GetPrivilegesRequest;
3940
import org.elasticsearch.client.security.GetRoleMappingsRequest;
4041
import org.elasticsearch.client.security.GetRolesRequest;
4142
import org.elasticsearch.client.security.GetUsersRequest;
4243
import org.elasticsearch.client.security.HasPrivilegesRequest;
4344
import org.elasticsearch.client.security.InvalidateApiKeyRequest;
45+
import org.elasticsearch.client.security.InvalidateMyApiKeyRequest;
4446
import org.elasticsearch.client.security.InvalidateTokenRequest;
4547
import org.elasticsearch.client.security.PutPrivilegesRequest;
4648
import org.elasticsearch.client.security.PutRoleMappingRequest;
@@ -285,10 +287,26 @@ static Request getApiKey(final GetApiKeyRequest getApiKeyRequest) throws IOExcep
285287
return request;
286288
}
287289

290+
static Request getMyApiKey(final GetMyApiKeyRequest getMyApiKeyRequest) throws IOException {
291+
final Request request = new Request(HttpGet.METHOD_NAME, "/_security/api_key/my");
292+
if (Strings.hasText(getMyApiKeyRequest.getId())) {
293+
request.addParameter("id", getMyApiKeyRequest.getId());
294+
}
295+
if (Strings.hasText(getMyApiKeyRequest.getName())) {
296+
request.addParameter("name", getMyApiKeyRequest.getName());
297+
}
298+
return request;
299+
}
300+
288301
static Request invalidateApiKey(final InvalidateApiKeyRequest invalidateApiKeyRequest) throws IOException {
289302
final Request request = new Request(HttpDelete.METHOD_NAME, "/_security/api_key");
290303
request.setEntity(createEntity(invalidateApiKeyRequest, REQUEST_BODY_CONTENT_TYPE));
291-
final RequestConverters.Params params = new RequestConverters.Params(request);
304+
return request;
305+
}
306+
307+
static Request invalidateMyApiKey(final InvalidateMyApiKeyRequest invalidateMyApiKeyRequest) throws IOException {
308+
final Request request = new Request(HttpDelete.METHOD_NAME, "/_security/api_key/my");
309+
request.setEntity(createEntity(invalidateMyApiKeyRequest, REQUEST_BODY_CONTENT_TYPE));
292310
return request;
293311
}
294312
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client.security;
21+
22+
import org.elasticsearch.client.Validatable;
23+
import org.elasticsearch.common.Nullable;
24+
import org.elasticsearch.common.xcontent.ToXContentObject;
25+
import org.elasticsearch.common.xcontent.XContentBuilder;
26+
27+
import java.io.IOException;
28+
29+
/**
30+
* Request for retrieving information for API key(s) owned by the authenticated user.
31+
*/
32+
public final class GetMyApiKeyRequest implements Validatable, ToXContentObject {
33+
34+
private final String id;
35+
private final String name;
36+
37+
public GetMyApiKeyRequest(@Nullable String apiKeyId, @Nullable String apiKeyName) {
38+
this.id = apiKeyId;
39+
this.name = apiKeyName;
40+
}
41+
42+
public String getId() {
43+
return id;
44+
}
45+
46+
public String getName() {
47+
return name;
48+
}
49+
50+
/**
51+
* Creates request for given api key id
52+
* @param apiKeyId api key id
53+
* @return {@link GetMyApiKeyRequest}
54+
*/
55+
public static GetMyApiKeyRequest usingApiKeyId(String apiKeyId) {
56+
return new GetMyApiKeyRequest(apiKeyId, null);
57+
}
58+
59+
/**
60+
* Creates request for given api key name
61+
* @param apiKeyName api key name
62+
* @return {@link GetMyApiKeyRequest}
63+
*/
64+
public static GetMyApiKeyRequest usingApiKeyName(String apiKeyName) {
65+
return new GetMyApiKeyRequest(null, apiKeyName);
66+
}
67+
68+
@Override
69+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
70+
return builder;
71+
}
72+
73+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client.security;
21+
22+
import org.elasticsearch.client.Validatable;
23+
import org.elasticsearch.common.Nullable;
24+
import org.elasticsearch.common.xcontent.ToXContentObject;
25+
import org.elasticsearch.common.xcontent.XContentBuilder;
26+
27+
import java.io.IOException;
28+
29+
/**
30+
* Request for invalidating API key(s) for the authenticated user so that it can no longer be used.
31+
*/
32+
public final class InvalidateMyApiKeyRequest implements Validatable, ToXContentObject {
33+
34+
private final String id;
35+
private final String name;
36+
37+
public InvalidateMyApiKeyRequest(@Nullable String apiKeyId, @Nullable String apiKeyName) {
38+
this.id = apiKeyId;
39+
this.name = apiKeyName;
40+
}
41+
42+
public String getId() {
43+
return id;
44+
}
45+
46+
public String getName() {
47+
return name;
48+
}
49+
50+
/**
51+
* Creates invalidate API key request for given api key id
52+
* @param apiKeyId api key id
53+
* @return {@link InvalidateMyApiKeyRequest}
54+
*/
55+
public static InvalidateMyApiKeyRequest usingApiKeyId(String apiKeyId) {
56+
return new InvalidateMyApiKeyRequest(apiKeyId, null);
57+
}
58+
59+
/**
60+
* Creates invalidate API key request for given api key name
61+
* @param apiKeyName api key name
62+
* @return {@link InvalidateMyApiKeyRequest}
63+
*/
64+
public static InvalidateMyApiKeyRequest usingApiKeyName(String apiKeyName) {
65+
return new InvalidateMyApiKeyRequest(null, apiKeyName);
66+
}
67+
68+
@Override
69+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
70+
builder.startObject();
71+
if (id != null) {
72+
builder.field("id", id);
73+
}
74+
if (name != null) {
75+
builder.field("name", name);
76+
}
77+
return builder.endObject();
78+
}
79+
}

client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,9 @@ public static class ClusterPrivilegeName {
311311
public static final String TRANSPORT_CLIENT = "transport_client";
312312
public static final String MANAGE_SECURITY = "manage_security";
313313
public static final String MANAGE_SAML = "manage_saml";
314+
public static final String MANAGE_API_KEY = "manage_api_key";
315+
public static final String OWNER_MANAGE_API_KEY = "owner_manage_api_key";
316+
public static final String CREATE_API_KEY = "create_api_key";
314317
public static final String MANAGE_TOKEN = "manage_token";
315318
public static final String MANAGE_PIPELINE = "manage_pipeline";
316319
public static final String MANAGE_CCR = "manage_ccr";
@@ -319,7 +322,8 @@ public static class ClusterPrivilegeName {
319322
public static final String READ_ILM = "read_ilm";
320323
public static final String[] ALL_ARRAY = new String[] { NONE, ALL, MONITOR, MONITOR_ML, MONITOR_WATCHER, MONITOR_ROLLUP, MANAGE,
321324
MANAGE_ML, MANAGE_WATCHER, MANAGE_ROLLUP, MANAGE_INDEX_TEMPLATES, MANAGE_INGEST_PIPELINES, TRANSPORT_CLIENT,
322-
MANAGE_SECURITY, MANAGE_SAML, MANAGE_TOKEN, MANAGE_PIPELINE, MANAGE_CCR, READ_CCR, MANAGE_ILM, READ_ILM };
325+
MANAGE_SECURITY, MANAGE_SAML, MANAGE_API_KEY, OWNER_MANAGE_API_KEY, CREATE_API_KEY, MANAGE_TOKEN, MANAGE_PIPELINE,
326+
MANAGE_CCR, READ_CCR, MANAGE_ILM, READ_ILM };
323327
}
324328

325329
/**

client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,13 @@
3333
import org.elasticsearch.client.security.DisableUserRequest;
3434
import org.elasticsearch.client.security.EnableUserRequest;
3535
import org.elasticsearch.client.security.GetApiKeyRequest;
36+
import org.elasticsearch.client.security.GetMyApiKeyRequest;
3637
import org.elasticsearch.client.security.GetPrivilegesRequest;
3738
import org.elasticsearch.client.security.GetRoleMappingsRequest;
3839
import org.elasticsearch.client.security.GetRolesRequest;
3940
import org.elasticsearch.client.security.GetUsersRequest;
4041
import org.elasticsearch.client.security.InvalidateApiKeyRequest;
42+
import org.elasticsearch.client.security.InvalidateMyApiKeyRequest;
4143
import org.elasticsearch.client.security.PutPrivilegesRequest;
4244
import org.elasticsearch.client.security.PutRoleMappingRequest;
4345
import org.elasticsearch.client.security.PutRoleRequest;
@@ -461,4 +463,24 @@ public void testInvalidateApiKey() throws IOException {
461463
assertEquals("/_security/api_key", request.getEndpoint());
462464
assertToXContentBody(invalidateApiKeyRequest, request.getEntity());
463465
}
466+
467+
public void testGetMyApiKey() throws IOException {
468+
String apiKeyId = randomAlphaOfLength(5);
469+
final GetMyApiKeyRequest getApiKeyRequest = GetMyApiKeyRequest.usingApiKeyId(apiKeyId);
470+
final Request request = SecurityRequestConverters.getMyApiKey(getApiKeyRequest);
471+
assertEquals(HttpGet.METHOD_NAME, request.getMethod());
472+
assertEquals("/_security/api_key/my", request.getEndpoint());
473+
Map<String, String> mapOfParameters = new HashMap<>();
474+
mapOfParameters.put("id", apiKeyId);
475+
assertThat(request.getParameters(), equalTo(mapOfParameters));
476+
}
477+
478+
public void testInvalidateMyApiKey() throws IOException {
479+
String apiKeyId = randomAlphaOfLength(5);
480+
final InvalidateMyApiKeyRequest invalidateApiKeyRequest = new InvalidateMyApiKeyRequest(apiKeyId, null);
481+
final Request request = SecurityRequestConverters.invalidateMyApiKey(invalidateApiKeyRequest);
482+
assertEquals(HttpDelete.METHOD_NAME, request.getMethod());
483+
assertEquals("/_security/api_key/my", request.getEndpoint());
484+
assertToXContentBody(invalidateApiKeyRequest, request.getEntity());
485+
}
464486
}

0 commit comments

Comments
 (0)