From 9e61a9c521e02b1d7eb05e50df07b4016fe0ec08 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad <902768+bizybot@users.noreply.github.com> Date: Wed, 7 Aug 2019 09:53:40 +1000 Subject: [PATCH 01/11] Simplify API key service API (#44935) This commit merely refactors API key service interface for retrieving and invalidating API keys. The service layer need not do any authorization so we do not need multiple interfaces to retrieve or invalidate API keys but one interface to do each operation. Relates #40031 --- .../action/TransportGetApiKeyAction.java | 11 +- .../TransportInvalidateApiKeyAction.java | 9 +- .../xpack/security/authc/ApiKeyService.java | 211 +++++------------- 3 files changed, 57 insertions(+), 174 deletions(-) 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..606f23b0fd190 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.getRealmName(), request.getUserName(), request.getApiKeyName(), request.getApiKeyId(), 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..9b552982fca9e 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.getRealmName(), request.getUserName(), request.getName(), request.getId(), 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 a660fe641456a..ec7c2b7870840 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 @@ -71,7 +71,6 @@ import org.elasticsearch.xpack.core.security.user.User; import org.elasticsearch.xpack.security.support.SecurityIndexManager; -import javax.crypto.SecretKeyFactory; import java.io.Closeable; import java.io.IOException; import java.io.UncheckedIOException; @@ -95,6 +94,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import javax.crypto.SecretKeyFactory; + import static org.elasticsearch.index.mapper.MapperService.SINGLE_MAPPING_NAME; import static org.elasticsearch.search.SearchService.DEFAULT_KEEPALIVE_SETTING; import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN; @@ -105,7 +106,7 @@ public class ApiKeyService { private static final Logger logger = LogManager.getLogger(ApiKeyService.class); private static final DeprecationLogger deprecationLogger = new DeprecationLogger(logger); - static final String API_KEY_ID_KEY = "_security_api_key_id"; + public static final String API_KEY_ID_KEY = "_security_api_key_id"; static final String API_KEY_ROLE_DESCRIPTORS_KEY = "_security_api_key_role_descriptors"; static final String API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY = "_security_api_key_limited_by_role_descriptors"; @@ -639,26 +640,34 @@ public void usedDeprecatedField(String usedName, String replacedWith) { } /** - * Invalidate API keys for given realm and user name. + * Invalidate API keys for given realm, user name, API key name and id. * @param realmName realm name - * @param userName user name + * @param username user name + * @param apiKeyName API key name + * @param apiKeyId API key id * @param invalidateListener listener for {@link InvalidateApiKeyResponse} */ - public void invalidateApiKeysForRealmAndUser(String realmName, String userName, - ActionListener invalidateListener) { + public void invalidateApiKeys(String realmName, String username, String apiKeyName, String apiKeyId, + 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")); + if (Strings.hasText(realmName) == false && Strings.hasText(username) == false && Strings.hasText(apiKeyName) == false + && Strings.hasText(apiKeyId) == false) { + logger.trace("none of the parameters [api key id, api key name, username, realm name] were specified for invalidation"); + invalidateListener + .onFailure(new IllegalArgumentException("One of [api key id, api key name, username, realm name] must be specified")); } 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)); + findApiKeysForUserRealmApiKeyIdAndNameCombination(realmName, username, apiKeyName, apiKeyId, true, false, + ActionListener.wrap(apiKeys -> { + if (apiKeys.isEmpty()) { + logger.debug( + "No active api keys to invalidate for realm [{}], username [{}], api key name [{}] and api key id [{}]", + realmName, username, apiKeyName, apiKeyId); + invalidateListener.onResponse(InvalidateApiKeyResponse.emptyResponse()); + } else { + invalidateAllApiKeys(apiKeys.stream().map(apiKey -> apiKey.getId()).collect(Collectors.toSet()), + invalidateListener); + } + }, invalidateListener::onFailure)); } } @@ -666,71 +675,6 @@ private void invalidateAllApiKeys(Collection apiKeyIds, ActionListener invalidateListener) { - ensureEnabled(); - if (Strings.hasText(apiKeyId) == false) { - logger.trace("No api key id provided"); - invalidateListener.onFailure(new IllegalArgumentException("api key id must be provided")); - } 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)); - } - } - - /** - * Invalidate API key for given API key name - * @param apiKeyName API key name - * @param invalidateListener listener for {@link InvalidateApiKeyResponse} - */ - public void invalidateApiKeyForApiKeyName(String apiKeyName, ActionListener invalidateListener) { - 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)); - } - } - - 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)); - } - - findApiKeys(boolQuery, filterOutInvalidatedKeys, filterOutExpiredKeys, listener); - } - } - private void findApiKeys(final BoolQueryBuilder boolQuery, boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys, ActionListener> listener) { if (filterOutInvalidatedKeys) { @@ -767,35 +711,28 @@ private void findApiKeys(final BoolQueryBuilder boolQuery, boolean filterOutInva } } - private void findApiKeyForApiKeyName(String apiKeyName, boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys, - ActionListener> listener) { + private void findApiKeysForUserRealmApiKeyIdAndNameCombination(String realmName, String userName, String apiKeyName, 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")); + final BoolQueryBuilder boolQuery = QueryBuilders.boolQuery().filter(QueryBuilders.termQuery("doc_type", "api_key")); + if (Strings.hasText(realmName)) { + boolQuery.filter(QueryBuilders.termQuery("creator.realm", realmName)); + } + if (Strings.hasText(userName)) { + boolQuery.filter(QueryBuilders.termQuery("creator.principal", userName)); + } 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); } @@ -818,9 +755,9 @@ private void indexInvalidation(Collection apiKeyIds, 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")); - } 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)); - } - } - - /** - * 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 -> { - if (apiKeyInfos.isEmpty()) { - logger.warn("No api key found for api key id [{}]", apiKeyId); - listener.onResponse(GetApiKeyResponse.emptyResponse()); - } else { - listener.onResponse(new GetApiKeyResponse(apiKeyInfos)); - } - }, listener::onFailure)); - } - } - - /** - * Get API key for given API key name + * @param username user name * @param apiKeyName API key name + * @param apiKeyId API key id * @param listener listener for {@link GetApiKeyResponse} */ - public void getApiKeyForApiKeyName(String apiKeyName, ActionListener listener) { + public void getApiKeys(String realmName, String username, String apiKeyName, String apiKeyId, + 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")); + if (Strings.hasText(realmName) == false && Strings.hasText(username) == false && Strings.hasText(apiKeyName) == false + && Strings.hasText(apiKeyId) == false) { + logger.trace("none of the parameters [api key id, api key name, username, realm name] were specified for retrieval"); + listener.onFailure(new IllegalArgumentException("One of [api key id, api key name, username, realm name] must be specified")); } else { - findApiKeyForApiKeyName(apiKeyName, false, false, ActionListener.wrap(apiKeyInfos -> { + findApiKeysForUserRealmApiKeyIdAndNameCombination(realmName, username, apiKeyName, apiKeyId, false, false, + ActionListener.wrap(apiKeyInfos -> { if (apiKeyInfos.isEmpty()) { - logger.warn("No api key found for api key name [{}]", apiKeyName); + logger.debug("No active api keys found for realm [{}], user [{}], api key name [{}] and api key id [{}]", + realmName, username, apiKeyName, apiKeyId); listener.onResponse(GetApiKeyResponse.emptyResponse()); } else { listener.onResponse(new GetApiKeyResponse(apiKeyInfos)); From 522f00593b111bbbda5926e61c8350b2ae158651 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad <902768+bizybot@users.noreply.github.com> Date: Wed, 7 Aug 2019 16:52:21 +1000 Subject: [PATCH 02/11] REST API changes for manage-own-api-key privilege (#44936) This commit adds a flag that can be set to `true` if the API key request (Get or Invalidate) is for the API keys owned by the currently authenticated user only. These only interface changes and once the actual cluster privilege `manage_own_api_key` is done, we will have another PR to make the interface work. The Get API behavior would be: - when `owner` is set to `true` `GET /_security/api_key?id=abcd&owner=true` the Rest controller will take care of setting `realm_name` and `username` to the values for the authenticated user and only return results if it finds one owned by the currently authenticated user. - when `owner` is set to `false` (default) `GET /_security/api_key?id=abcd` the Rest controller will assume `realm_name` and `username` to be unspecified meaning it will try to search for the API key across users and realms. This will fail if the user has only `manage_own_api_key` privilege. Similarly, for Delete API key behavior: - when `owner` is set to `true` `DELETE /_security/api_key` ``` { "id" : "VuaCfGcBCdbkQm-e5aOx", "owner": "true" } ``` the Rest controller will take care of setting `realm_name` and `username` to the values for the authenticated user and only invalidate key if it finds one owned by the currently authenticated user. - when `my_api_keys_only` is set to `false` (default) `DELETE /_security/api_key` ``` { "id" : "VuaCfGcBCdbkQm-e5aOx", "owner": "false" } ``` the Rest controller will assume `realm_name` and `username` to be unspecified meaning it will try to search for the API key across users and realms. This will fail if the user has only `manage_own_api_key` privilege. TODO: - HLRC changes - these will be done in a separate PR - Actual enforcement of `my_api_keys_only` in a separate PR Relates #40031 --- .../security/action/GetApiKeyRequest.java | 71 +++++++++++++--- .../action/InvalidateApiKeyRequest.java | 83 ++++++++++++++---- .../action/GetApiKeyRequestTests.java | 82 ++++++++++++++---- .../action/InvalidateApiKeyRequestTests.java | 84 +++++++++++++++---- .../action/apikey/RestGetApiKeyAction.java | 3 +- .../apikey/RestInvalidateApiKeyAction.java | 4 +- .../security/authc/ApiKeyIntegTests.java | 25 +++--- .../apikey/RestGetApiKeyActionTests.java | 73 +++++++++++++++- .../RestInvalidateApiKeyActionTests.java | 73 +++++++++++++++- 9 files changed, 424 insertions(+), 74 deletions(-) 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 125602f68c5e2..a84965127b2d3 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 @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.core.security.action; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.common.Nullable; @@ -14,6 +15,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import java.io.IOException; +import java.util.Objects; import static org.elasticsearch.action.ValidateActions.addValidationError; @@ -26,9 +28,10 @@ public final class GetApiKeyRequest extends ActionRequest { private final String userName; private final String apiKeyId; private final String apiKeyName; + private final boolean ownedByAuthenticatedUser; public GetApiKeyRequest() { - this(null, null, null, null); + this(null, null, null, null, false); } public GetApiKeyRequest(StreamInput in) throws IOException { @@ -37,14 +40,20 @@ public GetApiKeyRequest(StreamInput in) throws IOException { userName = in.readOptionalString(); apiKeyId = in.readOptionalString(); apiKeyName = in.readOptionalString(); + if (in.getVersion().onOrAfter(Version.V_7_4_0)) { + ownedByAuthenticatedUser = in.readOptionalBoolean(); + } else { + ownedByAuthenticatedUser = false; + } } public GetApiKeyRequest(@Nullable String realmName, @Nullable String userName, @Nullable String apiKeyId, - @Nullable String apiKeyName) { + @Nullable String apiKeyName, boolean ownedByAuthenticatedUser) { this.realmName = realmName; this.userName = userName; this.apiKeyId = apiKeyId; this.apiKeyName = apiKeyName; + this.ownedByAuthenticatedUser = ownedByAuthenticatedUser; } public String getRealmName() { @@ -63,13 +72,17 @@ public String getApiKeyName() { return apiKeyName; } + public boolean ownedByAuthenticatedUser() { + return ownedByAuthenticatedUser; + } + /** * Creates get API key request for given realm name * @param realmName realm name * @return {@link GetApiKeyRequest} */ public static GetApiKeyRequest usingRealmName(String realmName) { - return new GetApiKeyRequest(realmName, null, null, null); + return new GetApiKeyRequest(realmName, null, null, null, false); } /** @@ -78,7 +91,7 @@ public static GetApiKeyRequest usingRealmName(String realmName) { * @return {@link GetApiKeyRequest} */ public static GetApiKeyRequest usingUserName(String userName) { - return new GetApiKeyRequest(null, userName, null, null); + return new GetApiKeyRequest(null, userName, null, null, false); } /** @@ -88,34 +101,38 @@ public static GetApiKeyRequest usingUserName(String userName) { * @return {@link GetApiKeyRequest} */ public static GetApiKeyRequest usingRealmAndUserName(String realmName, String userName) { - return new GetApiKeyRequest(realmName, userName, null, null); + return new GetApiKeyRequest(realmName, userName, null, null, false); } /** * Creates get API key request for given api key id * @param apiKeyId api key id + * @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else + * {@code false} * @return {@link GetApiKeyRequest} */ - public static GetApiKeyRequest usingApiKeyId(String apiKeyId) { - return new GetApiKeyRequest(null, null, apiKeyId, null); + public static GetApiKeyRequest usingApiKeyId(String apiKeyId, boolean ownedByAuthenticatedUser) { + return new GetApiKeyRequest(null, null, apiKeyId, null, ownedByAuthenticatedUser); } /** * Creates get api key request for given api key name * @param apiKeyName api key name + * @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else + * {@code false} * @return {@link GetApiKeyRequest} */ - public static GetApiKeyRequest usingApiKeyName(String apiKeyName) { - return new GetApiKeyRequest(null, null, null, apiKeyName); + public static GetApiKeyRequest usingApiKeyName(String apiKeyName, boolean ownedByAuthenticatedUser) { + return new GetApiKeyRequest(null, null, null, apiKeyName, ownedByAuthenticatedUser); } @Override public ActionRequestValidationException validate() { ActionRequestValidationException validationException = null; if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(apiKeyId) == false - && Strings.hasText(apiKeyName) == false) { - validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified", - validationException); + && Strings.hasText(apiKeyName) == false && ownedByAuthenticatedUser == false) { + validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified if " + + "[owner] flag is false", validationException); } if (Strings.hasText(apiKeyId) || Strings.hasText(apiKeyName)) { if (Strings.hasText(realmName) || Strings.hasText(userName)) { @@ -124,6 +141,13 @@ public ActionRequestValidationException validate() { validationException); } } + if (ownedByAuthenticatedUser) { + if (Strings.hasText(realmName) || Strings.hasText(userName)) { + validationException = addValidationError( + "neither username nor realm-name may be specified when retrieving owned API keys", + validationException); + } + } if (Strings.hasText(apiKeyId) && Strings.hasText(apiKeyName)) { validationException = addValidationError("only one of [api key id, api key name] can be specified", validationException); } @@ -137,6 +161,29 @@ public void writeTo(StreamOutput out) throws IOException { out.writeOptionalString(userName); out.writeOptionalString(apiKeyId); out.writeOptionalString(apiKeyName); + if (out.getVersion().onOrAfter(Version.V_7_4_0)) { + out.writeOptionalBoolean(ownedByAuthenticatedUser); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + GetApiKeyRequest that = (GetApiKeyRequest) o; + return ownedByAuthenticatedUser == that.ownedByAuthenticatedUser && + Objects.equals(realmName, that.realmName) && + Objects.equals(userName, that.userName) && + Objects.equals(apiKeyId, that.apiKeyId) && + Objects.equals(apiKeyName, that.apiKeyName); } + @Override + public int hashCode() { + return Objects.hash(realmName, userName, apiKeyId, apiKeyName, ownedByAuthenticatedUser); } +} 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 15a2c87becd20..bca874ef9de39 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 @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.core.security.action; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.common.Nullable; @@ -14,6 +15,7 @@ import org.elasticsearch.common.io.stream.StreamOutput; import java.io.IOException; +import java.util.Objects; import static org.elasticsearch.action.ValidateActions.addValidationError; @@ -26,9 +28,10 @@ public final class InvalidateApiKeyRequest extends ActionRequest { private final String userName; private final String id; private final String name; + private final boolean ownedByAuthenticatedUser; public InvalidateApiKeyRequest() { - this(null, null, null, null); + this(null, null, null, null, false); } public InvalidateApiKeyRequest(StreamInput in) throws IOException { @@ -37,14 +40,20 @@ public InvalidateApiKeyRequest(StreamInput in) throws IOException { userName = in.readOptionalString(); id = in.readOptionalString(); name = in.readOptionalString(); + if (in.getVersion().onOrAfter(Version.V_7_4_0)) { + ownedByAuthenticatedUser = in.readOptionalBoolean(); + } else { + ownedByAuthenticatedUser = false; + } } public InvalidateApiKeyRequest(@Nullable String realmName, @Nullable String userName, @Nullable String id, - @Nullable String name) { + @Nullable String name, boolean ownedByAuthenticatedUser) { this.realmName = realmName; this.userName = userName; this.id = id; this.name = name; + this.ownedByAuthenticatedUser = ownedByAuthenticatedUser; } public String getRealmName() { @@ -63,65 +72,85 @@ public String getName() { return name; } + public boolean ownedByAuthenticatedUser() { + return ownedByAuthenticatedUser; + } + /** * Creates invalidate api key request for given realm name + * * @param realmName realm name * @return {@link InvalidateApiKeyRequest} */ public static InvalidateApiKeyRequest usingRealmName(String realmName) { - return new InvalidateApiKeyRequest(realmName, null, null, null); + return new InvalidateApiKeyRequest(realmName, null, null, null, false); } /** * Creates invalidate API key request for given user name + * * @param userName user name * @return {@link InvalidateApiKeyRequest} */ public static InvalidateApiKeyRequest usingUserName(String userName) { - return new InvalidateApiKeyRequest(null, userName, null, null); + return new InvalidateApiKeyRequest(null, userName, null, null, false); } /** * Creates invalidate API key request for given realm and user name + * * @param realmName realm name - * @param userName user name + * @param userName user name * @return {@link InvalidateApiKeyRequest} */ public static InvalidateApiKeyRequest usingRealmAndUserName(String realmName, String userName) { - return new InvalidateApiKeyRequest(realmName, userName, null, null); + return new InvalidateApiKeyRequest(realmName, userName, null, null, false); } /** * Creates invalidate API key request for given api key id + * * @param id api key id + * @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else + * {@code false} * @return {@link InvalidateApiKeyRequest} */ - public static InvalidateApiKeyRequest usingApiKeyId(String id) { - return new InvalidateApiKeyRequest(null, null, id, null); + public static InvalidateApiKeyRequest usingApiKeyId(String id, boolean ownedByAuthenticatedUser) { + return new InvalidateApiKeyRequest(null, null, id, null, ownedByAuthenticatedUser); } /** * Creates invalidate api key request for given api key name + * * @param name api key name + * @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else + * {@code false} * @return {@link InvalidateApiKeyRequest} */ - public static InvalidateApiKeyRequest usingApiKeyName(String name) { - return new InvalidateApiKeyRequest(null, null, null, name); + public static InvalidateApiKeyRequest usingApiKeyName(String name, boolean ownedByAuthenticatedUser) { + return new InvalidateApiKeyRequest(null, null, null, name, ownedByAuthenticatedUser); } @Override public ActionRequestValidationException validate() { ActionRequestValidationException validationException = null; if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(id) == false - && Strings.hasText(name) == false) { - validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified", - validationException); + && Strings.hasText(name) == false && ownedByAuthenticatedUser == false) { + validationException = addValidationError("One of [api key id, api key name, username, realm name] must be specified if " + + "[owner] flag is false", validationException); } if (Strings.hasText(id) || Strings.hasText(name)) { if (Strings.hasText(realmName) || Strings.hasText(userName)) { validationException = addValidationError( - "username or realm name must not be specified when the api key id or api key name is specified", - validationException); + "username or realm name must not be specified when the api key id or api key name is specified", + validationException); + } + } + if (ownedByAuthenticatedUser) { + if (Strings.hasText(realmName) || Strings.hasText(userName)) { + validationException = addValidationError( + "neither username nor realm-name may be specified when invalidating owned API keys", + validationException); } } if (Strings.hasText(id) && Strings.hasText(name)) { @@ -137,5 +166,29 @@ public void writeTo(StreamOutput out) throws IOException { out.writeOptionalString(userName); out.writeOptionalString(id); out.writeOptionalString(name); + if (out.getVersion().onOrAfter(Version.V_7_4_0)) { + out.writeOptionalBoolean(ownedByAuthenticatedUser); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + InvalidateApiKeyRequest that = (InvalidateApiKeyRequest) o; + return ownedByAuthenticatedUser == that.ownedByAuthenticatedUser && + Objects.equals(realmName, that.realmName) && + Objects.equals(userName, that.userName) && + Objects.equals(id, that.id) && + Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(realmName, userName, id, name, ownedByAuthenticatedUser); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequestTests.java index 27be0d88eb82c..1c5548af70a81 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequestTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/GetApiKeyRequestTests.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.core.security.action; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.common.io.stream.InputStreamStreamInput; @@ -17,15 +18,18 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import static org.elasticsearch.test.VersionUtils.randomVersionBetween; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; public class GetApiKeyRequestTests extends ESTestCase { public void testRequestValidation() { - GetApiKeyRequest request = GetApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5)); + GetApiKeyRequest request = GetApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5), randomBoolean()); ActionRequestValidationException ve = request.validate(); assertNull(ve); - request = GetApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5)); + request = GetApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5), randomBoolean()); ve = request.validate(); assertNull(ve); request = GetApiKeyRequest.usingRealmName(randomAlphaOfLength(5)); @@ -45,12 +49,14 @@ class Dummy extends ActionRequest { String user; String apiKeyId; String apiKeyName; + boolean ownedByAuthenticatedUser; Dummy(String[] a) { realm = a[0]; user = a[1]; apiKeyId = a[2]; apiKeyName = a[3]; + ownedByAuthenticatedUser = Boolean.parseBoolean(a[4]); } @Override @@ -65,23 +71,31 @@ public void writeTo(StreamOutput out) throws IOException { out.writeOptionalString(user); out.writeOptionalString(apiKeyId); out.writeOptionalString(apiKeyName); + out.writeOptionalBoolean(ownedByAuthenticatedUser); } } - String[][] inputs = new String[][] { - { randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), - randomFrom(new String[] { null, "" }) }, - { randomFrom(new String[] { null, "" }), "user", "api-kid", "api-kname" }, - { "realm", randomFrom(new String[] { null, "" }), "api-kid", "api-kname" }, - { "realm", "user", "api-kid", randomFrom(new String[] { null, "" }) }, - { randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), "api-kid", "api-kname" } }; - String[][] expectedErrorMessages = new String[][] { { "One of [api key id, api key name, username, realm name] must be specified" }, - { "username or realm name must not be specified when the api key id or api key name is specified", - "only one of [api key id, api key name] can be specified" }, - { "username or realm name must not be specified when the api key id or api key name is specified", - "only one of [api key id, api key name] can be specified" }, - { "username or realm name must not be specified when the api key id or api key name is specified" }, - { "only one of [api key id, api key name] can be specified" } }; + String[][] inputs = new String[][]{ + {randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), + randomNullOrEmptyString(), "false"}, + {randomNullOrEmptyString(), "user", "api-kid", "api-kname", "false"}, + {"realm", randomNullOrEmptyString(), "api-kid", "api-kname", "false"}, + {"realm", "user", "api-kid", randomNullOrEmptyString(), "false"}, + {randomNullOrEmptyString(), randomNullOrEmptyString(), "api-kid", "api-kname", "false"}, + {"realm", randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "true"}, + {randomNullOrEmptyString(), "user", randomNullOrEmptyString(), randomNullOrEmptyString(), "true"} + }; + String[][] expectedErrorMessages = new String[][]{ + {"One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false"}, + {"username or realm name must not be specified when the api key id or api key name is specified", + "only one of [api key id, api key name] can be specified"}, + {"username or realm name must not be specified when the api key id or api key name is specified", + "only one of [api key id, api key name] can be specified"}, + {"username or realm name must not be specified when the api key id or api key name is specified"}, + {"only one of [api key id, api key name] can be specified"}, + {"neither username nor realm-name may be specified when retrieving owned API keys"}, + {"neither username nor realm-name may be specified when retrieving owned API keys"} + }; for (int caseNo = 0; caseNo < inputs.length; caseNo++) { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -100,4 +114,40 @@ public void writeTo(StreamOutput out) throws IOException { } } } + + public void testSerialization() throws IOException { + final String apiKeyId = randomAlphaOfLength(5); + final boolean ownedByAuthenticatedUser = true; + GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(apiKeyId, ownedByAuthenticatedUser); + { + ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); + OutputStreamStreamOutput out = new OutputStreamStreamOutput(outBuffer); + out.setVersion(randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_3_0)); + getApiKeyRequest.writeTo(out); + + InputStreamStreamInput inputStreamStreamInput = new InputStreamStreamInput(new ByteArrayInputStream(outBuffer.toByteArray())); + inputStreamStreamInput.setVersion(randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_3_0)); + GetApiKeyRequest requestFromInputStream = new GetApiKeyRequest(inputStreamStreamInput); + + assertThat(requestFromInputStream.getApiKeyId(), equalTo(getApiKeyRequest.getApiKeyId())); + // old version so the default for `ownedByAuthenticatedUser` is false + assertThat(requestFromInputStream.ownedByAuthenticatedUser(), is(false)); + } + { + ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); + OutputStreamStreamOutput out = new OutputStreamStreamOutput(outBuffer); + out.setVersion(randomVersionBetween(random(), Version.V_7_4_0, Version.CURRENT)); + getApiKeyRequest.writeTo(out); + + InputStreamStreamInput inputStreamStreamInput = new InputStreamStreamInput(new ByteArrayInputStream(outBuffer.toByteArray())); + inputStreamStreamInput.setVersion(randomVersionBetween(random(), Version.V_7_4_0, Version.CURRENT)); + GetApiKeyRequest requestFromInputStream = new GetApiKeyRequest(inputStreamStreamInput); + + assertThat(requestFromInputStream, equalTo(getApiKeyRequest)); + } + } + + private static String randomNullOrEmptyString() { + return randomBoolean() ? "" : null; + } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequestTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequestTests.java index 3d7fd90234286..2f959c4841761 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequestTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/action/InvalidateApiKeyRequestTests.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.core.security.action; +import org.elasticsearch.Version; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.common.io.stream.InputStreamStreamInput; @@ -17,15 +18,18 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; +import static org.elasticsearch.test.VersionUtils.randomVersionBetween; import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; public class InvalidateApiKeyRequestTests extends ESTestCase { public void testRequestValidation() { - InvalidateApiKeyRequest request = InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5)); + InvalidateApiKeyRequest request = InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5), randomBoolean()); ActionRequestValidationException ve = request.validate(); assertNull(ve); - request = InvalidateApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5)); + request = InvalidateApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5), randomBoolean()); ve = request.validate(); assertNull(ve); request = InvalidateApiKeyRequest.usingRealmName(randomAlphaOfLength(5)); @@ -45,12 +49,14 @@ class Dummy extends ActionRequest { String user; String apiKeyId; String apiKeyName; + boolean ownedByAuthenticatedUser; Dummy(String[] a) { realm = a[0]; user = a[1]; apiKeyId = a[2]; apiKeyName = a[3]; + ownedByAuthenticatedUser = Boolean.parseBoolean(a[4]); } @Override @@ -65,24 +71,31 @@ public void writeTo(StreamOutput out) throws IOException { out.writeOptionalString(user); out.writeOptionalString(apiKeyId); out.writeOptionalString(apiKeyName); + out.writeOptionalBoolean(ownedByAuthenticatedUser); } } - String[][] inputs = new String[][] { - { randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), - randomFrom(new String[] { null, "" }) }, - { randomFrom(new String[] { null, "" }), "user", "api-kid", "api-kname" }, - { "realm", randomFrom(new String[] { null, "" }), "api-kid", "api-kname" }, - { "realm", "user", "api-kid", randomFrom(new String[] { null, "" }) }, - { randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), "api-kid", "api-kname" } }; - String[][] expectedErrorMessages = new String[][] { { "One of [api key id, api key name, username, realm name] must be specified" }, - { "username or realm name must not be specified when the api key id or api key name is specified", - "only one of [api key id, api key name] can be specified" }, - { "username or realm name must not be specified when the api key id or api key name is specified", - "only one of [api key id, api key name] can be specified" }, - { "username or realm name must not be specified when the api key id or api key name is specified" }, - { "only one of [api key id, api key name] can be specified" } }; - + String[][] inputs = new String[][]{ + {randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), + randomNullOrEmptyString(), "false"}, + {randomNullOrEmptyString(), "user", "api-kid", "api-kname", "false"}, + {"realm", randomNullOrEmptyString(), "api-kid", "api-kname", "false"}, + {"realm", "user", "api-kid", randomNullOrEmptyString(), "false"}, + {randomNullOrEmptyString(), randomNullOrEmptyString(), "api-kid", "api-kname", "false"}, + {"realm", randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "true"}, + {randomNullOrEmptyString(), "user", randomNullOrEmptyString(), randomNullOrEmptyString(), "true"}, + }; + String[][] expectedErrorMessages = new String[][]{ + {"One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false"}, + {"username or realm name must not be specified when the api key id or api key name is specified", + "only one of [api key id, api key name] can be specified"}, + {"username or realm name must not be specified when the api key id or api key name is specified", + "only one of [api key id, api key name] can be specified"}, + {"username or realm name must not be specified when the api key id or api key name is specified"}, + {"only one of [api key id, api key name] can be specified"}, + {"neither username nor realm-name may be specified when invalidating owned API keys"}, + {"neither username nor realm-name may be specified when invalidating owned API keys"} + }; for (int caseNo = 0; caseNo < inputs.length; caseNo++) { try (ByteArrayOutputStream bos = new ByteArrayOutputStream(); @@ -101,4 +114,41 @@ public void writeTo(StreamOutput out) throws IOException { } } } + + public void testSerialization() throws IOException { + final String apiKeyId = randomAlphaOfLength(5); + final boolean ownedByAuthenticatedUser = true; + InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(apiKeyId, ownedByAuthenticatedUser); + { + ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); + OutputStreamStreamOutput out = new OutputStreamStreamOutput(outBuffer); + out.setVersion(randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_3_0)); + invalidateApiKeyRequest.writeTo(out); + + InputStreamStreamInput inputStreamStreamInput = new InputStreamStreamInput(new ByteArrayInputStream(outBuffer.toByteArray())); + inputStreamStreamInput.setVersion(randomVersionBetween(random(), Version.V_7_0_0, Version.V_7_3_0)); + InvalidateApiKeyRequest requestFromInputStream = new InvalidateApiKeyRequest(inputStreamStreamInput); + + assertThat(requestFromInputStream.getId(), equalTo(invalidateApiKeyRequest.getId())); + // old version so the default for `ownedByAuthenticatedUser` is false + assertThat(requestFromInputStream.ownedByAuthenticatedUser(), is(false)); + } + { + ByteArrayOutputStream outBuffer = new ByteArrayOutputStream(); + OutputStreamStreamOutput out = new OutputStreamStreamOutput(outBuffer); + out.setVersion(randomVersionBetween(random(), Version.V_7_4_0, Version.CURRENT)); + invalidateApiKeyRequest.writeTo(out); + + InputStreamStreamInput inputStreamStreamInput = new InputStreamStreamInput(new ByteArrayInputStream(outBuffer.toByteArray())); + inputStreamStreamInput.setVersion(randomVersionBetween(random(), Version.V_7_4_0, Version.CURRENT)); + InvalidateApiKeyRequest requestFromInputStream = new InvalidateApiKeyRequest(inputStreamStreamInput); + + assertThat(requestFromInputStream, equalTo(invalidateApiKeyRequest)); + } + } + + private static String randomNullOrEmptyString() { + return randomFrom(new String[]{"", null}); + } + } 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..ca07952478444 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 @@ -39,7 +39,8 @@ protected RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClien final String apiKeyName = request.param("name"); final String userName = request.param("username"); final String realmName = request.param("realm_name"); - final GetApiKeyRequest getApiKeyRequest = new GetApiKeyRequest(realmName, userName, apiKeyId, apiKeyName); + final boolean myApiKeysOnly = request.paramAsBoolean("owner", false); + final GetApiKeyRequest getApiKeyRequest = new GetApiKeyRequest(realmName, userName, apiKeyId, apiKeyName, myApiKeysOnly); return channel -> client.execute(GetApiKeyAction.INSTANCE, getApiKeyRequest, new RestBuilderListener(channel) { @Override 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..0579932887677 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 @@ -31,7 +31,8 @@ public final class RestInvalidateApiKeyAction extends ApiKeyBaseRestHandler { static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("invalidate_api_key", a -> { - return new InvalidateApiKeyRequest((String) a[0], (String) a[1], (String) a[2], (String) a[3]); + return new InvalidateApiKeyRequest((String) a[0], (String) a[1], (String) a[2], (String) a[3], (a[4] == null) ? false : + (Boolean) a[4]); }); static { @@ -39,6 +40,7 @@ public final class RestInvalidateApiKeyAction extends ApiKeyBaseRestHandler { PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("username")); PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("id")); PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), new ParseField("name")); + PARSER.declareBoolean(ConstructingObjectParser.optionalConstructorArg(), new ParseField("owner")); } public RestInvalidateApiKeyAction(Settings settings, RestController controller, XPackLicenseState licenseState) { 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 5c6c04b6ad491..7309dfa2225d8 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 @@ -171,7 +171,7 @@ public void testCreateApiKeyFailsWhenApiKeyWithSameNameAlreadyExists() throws In // Now invalidate the API key PlainActionFuture listener = new PlainActionFuture<>(); - client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyName(keyName), listener); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyName(keyName, false), listener); InvalidateApiKeyResponse invalidateResponse = listener.get(); verifyInvalidateResponse(1, responses, invalidateResponse); @@ -222,7 +222,7 @@ public void testInvalidateApiKeysForApiKeyId() throws InterruptedException, Exec Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken .basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); PlainActionFuture listener = new PlainActionFuture<>(); - client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(responses.get(0).getId()), listener); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(responses.get(0).getId(), false), listener); InvalidateApiKeyResponse invalidateResponse = listener.get(); verifyInvalidateResponse(1, responses, invalidateResponse); } @@ -232,7 +232,8 @@ public void testInvalidateApiKeysForApiKeyName() throws InterruptedException, Ex Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken .basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); PlainActionFuture listener = new PlainActionFuture<>(); - client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyName(responses.get(0).getName()), listener); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyName(responses.get(0).getName(), false), + listener); InvalidateApiKeyResponse invalidateResponse = listener.get(); verifyInvalidateResponse(1, responses, invalidateResponse); } @@ -254,7 +255,8 @@ public void testInvalidatedApiKeysDeletedByRemover() throws Exception { List createdApiKeys = createApiKeys(2, null); PlainActionFuture listener = new PlainActionFuture<>(); - client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(createdApiKeys.get(0).getId()), listener); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(createdApiKeys.get(0).getId(), false), + listener); InvalidateApiKeyResponse invalidateResponse = listener.get(); assertThat(invalidateResponse.getInvalidatedApiKeys().size(), equalTo(1)); assertThat(invalidateResponse.getPreviouslyInvalidatedApiKeys().size(), equalTo(0)); @@ -270,7 +272,8 @@ public void testInvalidatedApiKeysDeletedByRemover() throws Exception { // invalidate API key to trigger remover listener = new PlainActionFuture<>(); - client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(createdApiKeys.get(1).getId()), listener); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(createdApiKeys.get(1).getId(), false), + listener); assertThat(listener.get().getInvalidatedApiKeys().size(), is(1)); awaitApiKeysRemoverCompletion(); @@ -343,7 +346,8 @@ public void testExpiredApiKeysBehaviorWhenKeysExpired1WeekBeforeAnd1DayBefore() // Invalidate to trigger the remover PlainActionFuture listener = new PlainActionFuture<>(); - client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(createdApiKeys.get(2).getId()), listener); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(createdApiKeys.get(2).getId(), false), + listener); assertThat(listener.get().getInvalidatedApiKeys().size(), is(1)); awaitApiKeysRemoverCompletion(); @@ -391,7 +395,7 @@ public void testActiveApiKeysWithNoExpirationNeverGetDeletedByRemover() throws E .basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); PlainActionFuture listener = new PlainActionFuture<>(); // trigger expired keys remover - client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(responses.get(1).getId()), listener); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(responses.get(1).getId(), false), listener); InvalidateApiKeyResponse invalidateResponse = listener.get(); assertThat(invalidateResponse.getInvalidatedApiKeys().size(), equalTo(1)); assertThat(invalidateResponse.getPreviouslyInvalidatedApiKeys().size(), equalTo(0)); @@ -414,7 +418,8 @@ public void testGetApiKeysForRealm() throws InterruptedException, ExecutionExcep Set expectedValidKeyIds = null; if (invalidate) { PlainActionFuture listener = new PlainActionFuture<>(); - client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(responses.get(0).getId()), listener); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(responses.get(0).getId(), false), + listener); InvalidateApiKeyResponse invalidateResponse = listener.get(); invalidatedApiKeyIds = invalidateResponse.getInvalidatedApiKeys(); expectedValidKeyIds = responses.stream().filter(o -> !o.getId().equals(responses.get(0).getId())).map(o -> o.getId()) @@ -459,7 +464,7 @@ public void testGetApiKeysForApiKeyId() throws InterruptedException, ExecutionEx Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken .basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); PlainActionFuture listener = new PlainActionFuture<>(); - client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(responses.get(0).getId()), listener); + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(responses.get(0).getId(), false), listener); GetApiKeyResponse response = listener.get(); verifyGetResponse(1, responses, response, Collections.singleton(responses.get(0).getId()), null); } @@ -469,7 +474,7 @@ public void testGetApiKeysForApiKeyName() throws InterruptedException, Execution Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken .basicAuthHeaderValue(SecuritySettingsSource.TEST_SUPERUSER, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); PlainActionFuture listener = new PlainActionFuture<>(); - client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyName(responses.get(0).getName()), listener); + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyName(responses.get(0).getName(), false), listener); GetApiKeyResponse response = listener.get(); verifyGetResponse(1, responses, response, Collections.singleton(responses.get(0).getId()), null); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyActionTests.java index c706a251dda35..d1046a175670c 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestGetApiKeyActionTests.java @@ -8,11 +8,11 @@ import org.apache.lucene.util.SetOnce; import org.elasticsearch.ElasticsearchSecurityException; -import org.elasticsearch.action.ActionType; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.ActionType; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.collect.MapBuilder; import org.elasticsearch.common.settings.Settings; @@ -36,6 +36,7 @@ import java.time.temporal.ChronoUnit; import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Map; import static org.hamcrest.Matchers.arrayContaining; @@ -133,6 +134,76 @@ void doExecute(ActionType action, Request request, ActionListener param; + if (isGetRequestForOwnedKeysOnly) { + param = mapBuilder().put("owner", Boolean.TRUE.toString()).map(); + } else { + param = mapBuilder().put("owner", Boolean.FALSE.toString()).put("realm_name", "realm-1").map(); + } + + final FakeRestRequest restRequest = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY) + .withParams(param).build(); + + final SetOnce responseSetOnce = new SetOnce<>(); + final RestChannel restChannel = new AbstractRestChannel(restRequest, randomBoolean()) { + @Override + public void sendResponse(RestResponse restResponse) { + responseSetOnce.set(restResponse); + } + }; + + final Instant creation = Instant.now(); + final Instant expiration = randomFrom(Arrays.asList(null, Instant.now().plus(10, ChronoUnit.DAYS))); + final ApiKey apiKey1 = new ApiKey("api-key-name-1", "api-key-id-1", creation, expiration, false, + "user-x", "realm-1"); + final ApiKey apiKey2 = new ApiKey("api-key-name-2", "api-key-id-2", creation, expiration, false, + "user-y", "realm-1"); + final GetApiKeyResponse getApiKeyResponseExpectedWhenOwnerFlagIsTrue = new GetApiKeyResponse(Collections.singletonList(apiKey1)); + final GetApiKeyResponse getApiKeyResponseExpectedWhenOwnerFlagIsFalse = new GetApiKeyResponse(List.of(apiKey1, apiKey2)); + + try (NodeClient client = new NodeClient(Settings.EMPTY, threadPool) { + @SuppressWarnings("unchecked") + @Override + public + void doExecute(ActionType action, Request request, ActionListener listener) { + GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request; + ActionRequestValidationException validationException = getApiKeyRequest.validate(); + if (validationException != null) { + listener.onFailure(validationException); + return; + } + + if (getApiKeyRequest.ownedByAuthenticatedUser()) { + listener.onResponse((Response) getApiKeyResponseExpectedWhenOwnerFlagIsTrue); + } else if (getApiKeyRequest.getRealmName() != null && getApiKeyRequest.getRealmName().equals("realm-1")) { + listener.onResponse((Response) getApiKeyResponseExpectedWhenOwnerFlagIsFalse); + } + } + }) { + final RestGetApiKeyAction restGetApiKeyAction = new RestGetApiKeyAction(Settings.EMPTY, mockRestController, mockLicenseState); + + restGetApiKeyAction.handleRequest(restRequest, restChannel, client); + + final RestResponse restResponse = responseSetOnce.get(); + assertNotNull(restResponse); + assertThat(restResponse.status(), is(RestStatus.OK)); + final GetApiKeyResponse actual = GetApiKeyResponse + .fromXContent(createParser(XContentType.JSON.xContent(), restResponse.content())); + if (isGetRequestForOwnedKeysOnly) { + assertThat(actual.getApiKeyInfos().length, is(1)); + assertThat(actual.getApiKeyInfos(), + arrayContaining(apiKey1)); + } else { + assertThat(actual.getApiKeyInfos().length, is(2)); + assertThat(actual.getApiKeyInfos(), + arrayContaining(apiKey1, apiKey2)); + } + } + + } + private static MapBuilder mapBuilder() { return MapBuilder.newMapBuilder(); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyActionTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyActionTests.java index 21e65c485fb2b..51f700ba44000 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyActionTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/rest/action/apikey/RestInvalidateApiKeyActionTests.java @@ -8,11 +8,11 @@ import org.apache.lucene.util.SetOnce; import org.elasticsearch.ElasticsearchSecurityException; -import org.elasticsearch.action.ActionType; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.action.ActionType; import org.elasticsearch.client.node.NodeClient; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.settings.Settings; @@ -24,6 +24,7 @@ import org.elasticsearch.rest.RestChannel; import org.elasticsearch.rest.RestController; import org.elasticsearch.rest.RestResponse; +import org.elasticsearch.rest.RestStatus; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.rest.FakeRestRequest; import org.elasticsearch.threadpool.ThreadPool; @@ -31,8 +32,11 @@ import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyResponse; import java.util.Collections; +import java.util.List; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -119,4 +123,71 @@ void doExecute(ActionType action, Request request, ActionListener responseSetOnce = new SetOnce<>(); + final RestChannel restChannel = new AbstractRestChannel(restRequest, randomBoolean()) { + @Override + public void sendResponse(RestResponse restResponse) { + responseSetOnce.set(restResponse); + } + }; + + final InvalidateApiKeyResponse invalidateApiKeyResponseExpectedWhenOwnerFlagIsTrue = new InvalidateApiKeyResponse( + List.of("api-key-id-1"), Collections.emptyList(), null); + final InvalidateApiKeyResponse invalidateApiKeyResponseExpectedWhenOwnerFlagIsFalse = new InvalidateApiKeyResponse( + List.of("api-key-id-1", "api-key-id-2"), Collections.emptyList(), null); + + try (NodeClient client = new NodeClient(Settings.EMPTY, threadPool) { + @SuppressWarnings("unchecked") + @Override + public + void doExecute(ActionType action, Request request, ActionListener listener) { + InvalidateApiKeyRequest invalidateApiKeyRequest = (InvalidateApiKeyRequest) request; + ActionRequestValidationException validationException = invalidateApiKeyRequest.validate(); + if (validationException != null) { + listener.onFailure(validationException); + return; + } + + if (invalidateApiKeyRequest.ownedByAuthenticatedUser()) { + listener.onResponse((Response) invalidateApiKeyResponseExpectedWhenOwnerFlagIsTrue); + } else if (invalidateApiKeyRequest.getRealmName() != null && invalidateApiKeyRequest.getRealmName().equals("realm-1")) { + listener.onResponse((Response) invalidateApiKeyResponseExpectedWhenOwnerFlagIsFalse); + } + } + }) { + final RestInvalidateApiKeyAction restInvalidateApiKeyAction = new RestInvalidateApiKeyAction(Settings.EMPTY, mockRestController, + mockLicenseState); + + restInvalidateApiKeyAction.handleRequest(restRequest, restChannel, client); + + final RestResponse restResponse = responseSetOnce.get(); + assertNotNull(restResponse); + assertThat(restResponse.status(), is(RestStatus.OK)); + final InvalidateApiKeyResponse actual = InvalidateApiKeyResponse + .fromXContent(createParser(XContentType.JSON.xContent(), restResponse.content())); + if (isInvalidateRequestForOwnedKeysOnly) { + assertThat(actual.getInvalidatedApiKeys().size(), is(1)); + assertThat(actual.getInvalidatedApiKeys(), + containsInAnyOrder("api-key-id-1")); + } else { + assertThat(actual.getInvalidatedApiKeys().size(), is(2)); + assertThat(actual.getInvalidatedApiKeys(), + containsInAnyOrder("api-key-id-1", "api-key-id-2")); + } + } + + } } From 1950e381c43b99c04dd7b3bc98f8026a97e10ce3 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad <902768+bizybot@users.noreply.github.com> Date: Wed, 14 Aug 2019 11:11:58 +1000 Subject: [PATCH 03/11] Consider `owner` flag when retrieving/invalidating keys with API key service (#45421) Actual invocation of API key service now takes `owner` flag from the request into consideration by setting the values for `realm` and `username` as per the current authentication before invoking API key service. This allows for retrieving or invalidating API keys owned by the current authenticated user. Relates: #40031 --- .../security/action/GetApiKeyRequest.java | 7 ++ .../action/InvalidateApiKeyRequest.java | 7 ++ .../action/TransportGetApiKeyAction.java | 26 ++++++- .../TransportInvalidateApiKeyAction.java | 28 +++++++- .../security/authc/ApiKeyIntegTests.java | 72 +++++++++++++++++-- 5 files changed, 130 insertions(+), 10 deletions(-) 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 a84965127b2d3..f08cebe8141f4 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 @@ -126,6 +126,13 @@ public static GetApiKeyRequest usingApiKeyName(String apiKeyName, boolean ownedB return new GetApiKeyRequest(null, null, null, apiKeyName, ownedByAuthenticatedUser); } + /** + * Creates get api key request to retrieve api key information for the api keys owned by the current authenticated user. + */ + public static GetApiKeyRequest forOwnedApiKeys() { + return new GetApiKeyRequest(null, null, null, null, true); + } + @Override public ActionRequestValidationException validate() { ActionRequestValidationException validationException = null; 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 bca874ef9de39..6d26133479adf 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 @@ -131,6 +131,13 @@ public static InvalidateApiKeyRequest usingApiKeyName(String name, boolean owned return new InvalidateApiKeyRequest(null, null, null, name, ownedByAuthenticatedUser); } + /** + * Creates invalidate api key request to invalidate api keys owned by the current authenticated user. + */ + public static InvalidateApiKeyRequest forOwnedApiKeys() { + return new InvalidateApiKeyRequest(null, null, null, null, true); + } + @Override public ActionRequestValidationException validate() { ActionRequestValidationException validationException = null; 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 606f23b0fd190..9a3c0ed8326c9 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 @@ -13,25 +13,47 @@ import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.tasks.Task; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.security.SecurityContext; 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.authc.Authentication; import org.elasticsearch.xpack.security.authc.ApiKeyService; public final class TransportGetApiKeyAction extends HandledTransportAction { private final ApiKeyService apiKeyService; + private final SecurityContext securityContext; @Inject - public TransportGetApiKeyAction(TransportService transportService, ActionFilters actionFilters, ApiKeyService apiKeyService) { + public TransportGetApiKeyAction(TransportService transportService, ActionFilters actionFilters, ApiKeyService apiKeyService, + SecurityContext context) { super(GetApiKeyAction.NAME, transportService, actionFilters, (Writeable.Reader) GetApiKeyRequest::new); this.apiKeyService = apiKeyService; + this.securityContext = context; } @Override protected void doExecute(Task task, GetApiKeyRequest request, ActionListener listener) { - apiKeyService.getApiKeys(request.getRealmName(), request.getUserName(), request.getApiKeyName(), request.getApiKeyId(), listener); + String apiKeyId = request.getApiKeyId(); + String apiKeyName = request.getApiKeyName(); + String username = request.getUserName(); + String realm = request.getRealmName(); + + final Authentication authentication = securityContext.getAuthentication(); + if (authentication == null) { + listener.onFailure(new IllegalStateException("authentication is required")); + } + if (request.ownedByAuthenticatedUser()) { + assert username == null; + assert realm == null; + // restrict username and realm to current authenticated user. + username = authentication.getUser().principal(); + realm = authentication.getAuthenticatedBy().getName(); + } + + apiKeyService.getApiKeys(realm, username, apiKeyName, apiKeyId, 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 9b552982fca9e..0c4f7e395112a 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 @@ -13,25 +13,47 @@ import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.tasks.Task; import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.core.security.SecurityContext; 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.authc.Authentication; import org.elasticsearch.xpack.security.authc.ApiKeyService; public final class TransportInvalidateApiKeyAction extends HandledTransportAction { private final ApiKeyService apiKeyService; + private final SecurityContext securityContext; @Inject - public TransportInvalidateApiKeyAction(TransportService transportService, ActionFilters actionFilters, ApiKeyService apiKeyService) { + public TransportInvalidateApiKeyAction(TransportService transportService, ActionFilters actionFilters, ApiKeyService apiKeyService, + SecurityContext context) { super(InvalidateApiKeyAction.NAME, transportService, actionFilters, - (Writeable.Reader) InvalidateApiKeyRequest::new); + (Writeable.Reader) InvalidateApiKeyRequest::new); this.apiKeyService = apiKeyService; + this.securityContext = context; } @Override protected void doExecute(Task task, InvalidateApiKeyRequest request, ActionListener listener) { - apiKeyService.invalidateApiKeys(request.getRealmName(), request.getUserName(), request.getName(), request.getId(), listener); + String apiKeyId = request.getId(); + String apiKeyName = request.getName(); + String username = request.getUserName(); + String realm = request.getRealmName(); + + final Authentication authentication = securityContext.getAuthentication(); + if (authentication == null) { + listener.onFailure(new IllegalStateException("authentication is required")); + } + if (request.ownedByAuthenticatedUser()) { + assert username == null; + assert realm == null; + // restrict username and realm to current authenticated user. + username = authentication.getUser().principal(); + realm = authentication.getAuthenticatedBy().getName(); + } + + apiKeyService.invalidateApiKeys(realm, username, apiKeyName, apiKeyId, listener); } } 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 7309dfa2225d8..f73719cf25937 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 @@ -88,6 +88,27 @@ public void wipeSecurityIndex() throws InterruptedException { deleteSecurityIndex(); } + @Override + public String configRoles() { + return super.configRoles() + "\n" + + "manage_api_key_role:\n" + + " cluster: [\"manage_api_key\"]\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"; + } + + @Override + public String configUsersRoles() { + return super.configUsersRoles() + + "manage_api_key_role:user_with_manage_api_key_role\n"; + } + private void awaitApiKeysRemoverCompletion() throws InterruptedException { for (ApiKeyService apiKeyService : internalCluster().getInstances(ApiKeyService.class)) { final boolean done = awaitBusy(() -> apiKeyService.isExpirationInProgress() == false); @@ -479,10 +500,48 @@ public void testGetApiKeysForApiKeyName() throws InterruptedException, Execution verifyGetResponse(1, responses, response, Collections.singleton(responses.get(0).getId()), null); } - private void verifyGetResponse(int noOfApiKeys, List responses, GetApiKeyResponse response, + public void testGetApiKeysOwnedByCurrentAuthenticatedUser() throws InterruptedException, ExecutionException { + int noOfSuperuserApiKeys = randomIntBetween(3, 5); + int noOfApiKeysForUserWithManageApiKeyRole = randomIntBetween(3, 5); + List defaultUserCreatedKeys = createApiKeys(noOfSuperuserApiKeys, null); + List userWithManageApiKeyRoleApiKeys = createApiKeys("user_with_manage_api_key_role", + noOfApiKeysForUserWithManageApiKeyRole, null); + final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken + .basicAuthHeaderValue("user_with_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + + PlainActionFuture listener = new PlainActionFuture<>(); + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.forOwnedApiKeys(), listener); + GetApiKeyResponse response = listener.get(); + verifyGetResponse("user_with_manage_api_key_role", noOfApiKeysForUserWithManageApiKeyRole, userWithManageApiKeyRoleApiKeys, + response, userWithManageApiKeyRoleApiKeys.stream().map(o -> o.getId()).collect(Collectors.toSet()), null); + } + + public void testInvalidateApiKeysOwnedByCurrentAuthenticatedUser() throws InterruptedException, ExecutionException { + int noOfSuperuserApiKeys = randomIntBetween(3, 5); + int noOfApiKeysForUserWithManageApiKeyRole = randomIntBetween(3, 5); + List defaultUserCreatedKeys = createApiKeys(noOfSuperuserApiKeys, null); + List userWithManageApiKeyRoleApiKeys = createApiKeys("user_with_manage_api_key_role", + noOfApiKeysForUserWithManageApiKeyRole, null); + final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken + .basicAuthHeaderValue("user_with_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + + PlainActionFuture listener = new PlainActionFuture<>(); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.forOwnedApiKeys(), listener); + InvalidateApiKeyResponse invalidateResponse = listener.get(); + + verifyInvalidateResponse(noOfApiKeysForUserWithManageApiKeyRole, userWithManageApiKeyRoleApiKeys, invalidateResponse); + } + + private void verifyGetResponse(int expectedNumberOfApiKeys, List responses, GetApiKeyResponse response, Set validApiKeyIds, List invalidatedApiKeyIds) { - assertThat(response.getApiKeyInfos().length, equalTo(noOfApiKeys)); + verifyGetResponse(SecuritySettingsSource.TEST_SUPERUSER, expectedNumberOfApiKeys, responses, response, validApiKeyIds, + invalidatedApiKeyIds); + } + + private void verifyGetResponse(String user, int expectedNumberOfApiKeys, List responses, + GetApiKeyResponse response, Set validApiKeyIds, List invalidatedApiKeyIds) { + assertThat(response.getApiKeyInfos().length, equalTo(expectedNumberOfApiKeys)); List expectedIds = responses.stream().filter(o -> validApiKeyIds.contains(o.getId())).map(o -> o.getId()) .collect(Collectors.toList()); List actualIds = Arrays.stream(response.getApiKeyInfos()).filter(o -> o.isInvalidated() == false).map(o -> o.getId()) @@ -494,7 +553,7 @@ private void verifyGetResponse(int noOfApiKeys, List respo .collect(Collectors.toList()); assertThat(actualNames, containsInAnyOrder(expectedNames.toArray(Strings.EMPTY_ARRAY))); Set expectedUsernames = (validApiKeyIds.isEmpty()) ? Collections.emptySet() - : Collections.singleton(SecuritySettingsSource.TEST_SUPERUSER); + : Set.of(user); Set actualUsernames = Arrays.stream(response.getApiKeyInfos()).filter(o -> o.isInvalidated() == false) .map(o -> o.getUsername()).collect(Collectors.toSet()); assertThat(actualUsernames, containsInAnyOrder(expectedUsernames.toArray(Strings.EMPTY_ARRAY))); @@ -503,15 +562,18 @@ private void verifyGetResponse(int noOfApiKeys, List respo .map(o -> o.getId()).collect(Collectors.toList()); assertThat(invalidatedApiKeyIds, containsInAnyOrder(actualInvalidatedApiKeyIds.toArray(Strings.EMPTY_ARRAY))); } - } private List createApiKeys(int noOfApiKeys, TimeValue expiration) { + return createApiKeys(SecuritySettingsSource.TEST_SUPERUSER, noOfApiKeys, 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))); final CreateApiKeyResponse response = new CreateApiKeyRequestBuilder(client) .setName("test-key-" + randomAlphaOfLengthBetween(5, 9) + i).setExpiration(expiration) .setRoleDescriptors(Collections.singletonList(descriptor)).get(); From 9cf24321fbbeabbb2f1e9afff2a7e4999455cce7 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad <902768+bizybot@users.noreply.github.com> Date: Thu, 15 Aug 2019 21:51:40 +1000 Subject: [PATCH 04/11] HLRC: add support for retrieval/invalidation of owned API keys (#45474) This commit updates Rest client(get/invalidate API keys APIs) to support retrieval or invalidation of API keys owned by the currently authenticated user. Relates: #40031 --- .../client/SecurityRequestConverters.java | 2 +- .../client/security/GetApiKeyRequest.java | 42 ++++++++++++---- .../security/InvalidateApiKeyRequest.java | 43 ++++++++++++---- .../SecurityRequestConvertersTests.java | 9 ++-- .../SecurityDocumentationIT.java | 49 ++++++++++++++++--- .../security/GetApiKeyRequestTests.java | 34 ++++++++----- .../InvalidateApiKeyRequestTests.java | 34 ++++++++----- .../high-level/security/get-api-key.asciidoc | 8 +++ .../security/invalidate-api-key.asciidoc | 8 +++ 9 files changed, 176 insertions(+), 53 deletions(-) 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 18ecc2cea281a..ebe125dbe25e6 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 @@ -294,7 +294,7 @@ static Request getApiKey(final GetApiKeyRequest getApiKeyRequest) throws IOExcep if (Strings.hasText(getApiKeyRequest.getRealmName())) { request.addParameter("realm_name", getApiKeyRequest.getRealmName()); } - + request.addParameter("owner", Boolean.toString(getApiKeyRequest.ownedByAuthenticatedUser())); return request; } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetApiKeyRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetApiKeyRequest.java index 6fa98ec549b07..9427489786387 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetApiKeyRequest.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetApiKeyRequest.java @@ -36,13 +36,14 @@ public final class GetApiKeyRequest implements Validatable, ToXContentObject { private final String userName; private final String id; private final String name; + private final boolean ownedByAuthenticatedUser; // pkg scope for testing GetApiKeyRequest(@Nullable String realmName, @Nullable String userName, @Nullable String apiKeyId, - @Nullable String apiKeyName) { + @Nullable String apiKeyName, boolean ownedByAuthenticatedUser) { if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(apiKeyId) == false - && Strings.hasText(apiKeyName) == false) { - throwValidationError("One of [api key id, api key name, username, realm name] must be specified"); + && Strings.hasText(apiKeyName) == false && ownedByAuthenticatedUser == false) { + throwValidationError("One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false"); } if (Strings.hasText(apiKeyId) || Strings.hasText(apiKeyName)) { if (Strings.hasText(realmName) || Strings.hasText(userName)) { @@ -50,6 +51,11 @@ public final class GetApiKeyRequest implements Validatable, ToXContentObject { "username or realm name must not be specified when the api key id or api key name is specified"); } } + if (ownedByAuthenticatedUser) { + if (Strings.hasText(realmName) || Strings.hasText(userName)) { + throwValidationError("neither username nor realm-name may be specified when retrieving owned API keys"); + } + } if (Strings.hasText(apiKeyId) && Strings.hasText(apiKeyName)) { throwValidationError("only one of [api key id, api key name] can be specified"); } @@ -57,6 +63,7 @@ public final class GetApiKeyRequest implements Validatable, ToXContentObject { this.userName = userName; this.id = apiKeyId; this.name = apiKeyName; + this.ownedByAuthenticatedUser = ownedByAuthenticatedUser; } private void throwValidationError(String message) { @@ -79,13 +86,17 @@ public String getName() { return name; } + public boolean ownedByAuthenticatedUser() { + return ownedByAuthenticatedUser; + } + /** * Creates get API key request for given realm name * @param realmName realm name * @return {@link GetApiKeyRequest} */ public static GetApiKeyRequest usingRealmName(String realmName) { - return new GetApiKeyRequest(realmName, null, null, null); + return new GetApiKeyRequest(realmName, null, null, null, false); } /** @@ -94,7 +105,7 @@ public static GetApiKeyRequest usingRealmName(String realmName) { * @return {@link GetApiKeyRequest} */ public static GetApiKeyRequest usingUserName(String userName) { - return new GetApiKeyRequest(null, userName, null, null); + return new GetApiKeyRequest(null, userName, null, null, false); } /** @@ -104,25 +115,36 @@ public static GetApiKeyRequest usingUserName(String userName) { * @return {@link GetApiKeyRequest} */ public static GetApiKeyRequest usingRealmAndUserName(String realmName, String userName) { - return new GetApiKeyRequest(realmName, userName, null, null); + return new GetApiKeyRequest(realmName, userName, null, null, false); } /** * Creates get API key request for given api key id * @param apiKeyId api key id + * @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current + * authenticated user else{@code false} * @return {@link GetApiKeyRequest} */ - public static GetApiKeyRequest usingApiKeyId(String apiKeyId) { - return new GetApiKeyRequest(null, null, apiKeyId, null); + public static GetApiKeyRequest usingApiKeyId(String apiKeyId, boolean ownedByAuthenticatedUser) { + return new GetApiKeyRequest(null, null, apiKeyId, null, ownedByAuthenticatedUser); } /** * Creates get API key request for given api key name * @param apiKeyName api key name + * @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current + * authenticated user else{@code false} * @return {@link GetApiKeyRequest} */ - public static GetApiKeyRequest usingApiKeyName(String apiKeyName) { - return new GetApiKeyRequest(null, null, null, apiKeyName); + public static GetApiKeyRequest usingApiKeyName(String apiKeyName, boolean ownedByAuthenticatedUser) { + return new GetApiKeyRequest(null, null, null, apiKeyName, ownedByAuthenticatedUser); + } + + /** + * Creates get api key request to retrieve api key information for the api keys owned by the current authenticated user. + */ + public static GetApiKeyRequest forOwnedApiKeys() { + return new GetApiKeyRequest(null, null, null, null, true); } @Override diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateApiKeyRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateApiKeyRequest.java index d3203354b7ab1..351294e36d38b 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateApiKeyRequest.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/InvalidateApiKeyRequest.java @@ -36,13 +36,14 @@ public final class InvalidateApiKeyRequest implements Validatable, ToXContentObj private final String userName; private final String id; private final String name; + private final boolean ownedByAuthenticatedUser; // pkg scope for testing InvalidateApiKeyRequest(@Nullable String realmName, @Nullable String userName, @Nullable String apiKeyId, - @Nullable String apiKeyName) { + @Nullable String apiKeyName, boolean ownedByAuthenticatedUser) { if (Strings.hasText(realmName) == false && Strings.hasText(userName) == false && Strings.hasText(apiKeyId) == false - && Strings.hasText(apiKeyName) == false) { - throwValidationError("One of [api key id, api key name, username, realm name] must be specified"); + && Strings.hasText(apiKeyName) == false && ownedByAuthenticatedUser == false) { + throwValidationError("One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false"); } if (Strings.hasText(apiKeyId) || Strings.hasText(apiKeyName)) { if (Strings.hasText(realmName) || Strings.hasText(userName)) { @@ -50,6 +51,11 @@ public final class InvalidateApiKeyRequest implements Validatable, ToXContentObj "username or realm name must not be specified when the api key id or api key name is specified"); } } + if (ownedByAuthenticatedUser) { + if (Strings.hasText(realmName) || Strings.hasText(userName)) { + throwValidationError("neither username nor realm-name may be specified when invalidating owned API keys"); + } + } if (Strings.hasText(apiKeyId) && Strings.hasText(apiKeyName)) { throwValidationError("only one of [api key id, api key name] can be specified"); } @@ -57,6 +63,7 @@ public final class InvalidateApiKeyRequest implements Validatable, ToXContentObj this.userName = userName; this.id = apiKeyId; this.name = apiKeyName; + this.ownedByAuthenticatedUser = ownedByAuthenticatedUser; } private void throwValidationError(String message) { @@ -79,13 +86,17 @@ public String getName() { return name; } + public boolean ownedByAuthenticatedUser() { + return ownedByAuthenticatedUser; + } + /** * Creates invalidate API key request for given realm name * @param realmName realm name * @return {@link InvalidateApiKeyRequest} */ public static InvalidateApiKeyRequest usingRealmName(String realmName) { - return new InvalidateApiKeyRequest(realmName, null, null, null); + return new InvalidateApiKeyRequest(realmName, null, null, null, false); } /** @@ -94,7 +105,7 @@ public static InvalidateApiKeyRequest usingRealmName(String realmName) { * @return {@link InvalidateApiKeyRequest} */ public static InvalidateApiKeyRequest usingUserName(String userName) { - return new InvalidateApiKeyRequest(null, userName, null, null); + return new InvalidateApiKeyRequest(null, userName, null, null, false); } /** @@ -104,25 +115,36 @@ public static InvalidateApiKeyRequest usingUserName(String userName) { * @return {@link InvalidateApiKeyRequest} */ public static InvalidateApiKeyRequest usingRealmAndUserName(String realmName, String userName) { - return new InvalidateApiKeyRequest(realmName, userName, null, null); + return new InvalidateApiKeyRequest(realmName, userName, null, null, false); } /** * Creates invalidate API key request for given api key id * @param apiKeyId api key id + * @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else + * {@code false} * @return {@link InvalidateApiKeyRequest} */ - public static InvalidateApiKeyRequest usingApiKeyId(String apiKeyId) { - return new InvalidateApiKeyRequest(null, null, apiKeyId, null); + public static InvalidateApiKeyRequest usingApiKeyId(String apiKeyId, boolean ownedByAuthenticatedUser) { + return new InvalidateApiKeyRequest(null, null, apiKeyId, null, ownedByAuthenticatedUser); } /** * Creates invalidate API key request for given api key name * @param apiKeyName api key name + * @param ownedByAuthenticatedUser set {@code true} if the request is only for the API keys owned by current authenticated user else + * {@code false} * @return {@link InvalidateApiKeyRequest} */ - public static InvalidateApiKeyRequest usingApiKeyName(String apiKeyName) { - return new InvalidateApiKeyRequest(null, null, null, apiKeyName); + public static InvalidateApiKeyRequest usingApiKeyName(String apiKeyName, boolean ownedByAuthenticatedUser) { + return new InvalidateApiKeyRequest(null, null, null, apiKeyName, ownedByAuthenticatedUser); + } + + /** + * Creates invalidate api key request to invalidate api keys owned by the current authenticated user. + */ + public static InvalidateApiKeyRequest forOwnedApiKeys() { + return new InvalidateApiKeyRequest(null, null, null, null, true); } @Override @@ -140,6 +162,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (name != null) { builder.field("name", name); } + builder.field("owner", ownedByAuthenticatedUser); return builder.endObject(); } } 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 4c99cb323969e..51f77cb972ff6 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 @@ -446,10 +446,11 @@ public void testGetApiKey() throws IOException { final Request request = SecurityRequestConverters.getApiKey(getApiKeyRequest); assertEquals(HttpGet.METHOD_NAME, request.getMethod()); assertEquals("/_security/api_key", request.getEndpoint()); - Map mapOfParameters = new HashMap<>(); - mapOfParameters.put("realm_name", realmName); - mapOfParameters.put("username", userName); - assertThat(request.getParameters(), equalTo(mapOfParameters)); + Map expectedMapOfParameters = new HashMap<>(); + expectedMapOfParameters.put("realm_name", realmName); + expectedMapOfParameters.put("username", userName); + expectedMapOfParameters.put("owner", Boolean.FALSE.toString()); + assertThat(request.getParameters(), equalTo(expectedMapOfParameters)); } public void testInvalidateApiKey() throws IOException { 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 9bbc3b2ea9072..d4f782eaaee47 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 @@ -1917,7 +1917,7 @@ public void testGetApiKey() throws Exception { Instant.now().plusMillis(expiration.getMillis()), false, "test_user", "default_file"); { // tag::get-api-key-id-request - GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId()); + GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId(), false); // end::get-api-key-id-request // tag::get-api-key-execute @@ -1931,7 +1931,7 @@ public void testGetApiKey() throws Exception { { // tag::get-api-key-name-request - GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyName(createApiKeyResponse1.getName()); + GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyName(createApiKeyResponse1.getName(), false); // end::get-api-key-name-request GetApiKeyResponse getApiKeyResponse = client.security().getApiKey(getApiKeyRequest, RequestOptions.DEFAULT); @@ -1965,6 +1965,18 @@ public void testGetApiKey() throws Exception { verifyApiKey(getApiKeyResponse.getApiKeyInfos().get(0), expectedApiKeyInfo); } + { + // tag::get-api-keys-owned-by-authenticated-user-request + GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.forOwnedApiKeys(); + // end::get-api-keys-owned-by-authenticated-user-request + + GetApiKeyResponse getApiKeyResponse = client.security().getApiKey(getApiKeyRequest, RequestOptions.DEFAULT); + + assertThat(getApiKeyResponse.getApiKeyInfos(), is(notNullValue())); + assertThat(getApiKeyResponse.getApiKeyInfos().size(), is(1)); + verifyApiKey(getApiKeyResponse.getApiKeyInfos().get(0), expectedApiKeyInfo); + } + { // tag::get-user-realm-api-keys-request GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingRealmAndUserName("default_file", "test_user"); @@ -1980,7 +1992,7 @@ public void testGetApiKey() throws Exception { } { - GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId()); + GetApiKeyRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId(), false); ActionListener listener; // tag::get-api-key-execute-listener @@ -2041,7 +2053,7 @@ public void testInvalidateApiKey() throws Exception { { // tag::invalidate-api-key-id-request - InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId()); + InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(createApiKeyResponse1.getId(), false); // end::invalidate-api-key-id-request // tag::invalidate-api-key-execute @@ -2066,7 +2078,8 @@ public void testInvalidateApiKey() throws Exception { assertNotNull(createApiKeyResponse2.getKey()); // tag::invalidate-api-key-name-request - InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyName(createApiKeyResponse2.getName()); + InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyName(createApiKeyResponse2.getName(), + false); // end::invalidate-api-key-name-request InvalidateApiKeyResponse invalidateApiKeyResponse = client.security().invalidateApiKey(invalidateApiKeyRequest, @@ -2159,7 +2172,7 @@ public void testInvalidateApiKey() throws Exception { assertThat(createApiKeyResponse6.getName(), equalTo("k6")); assertNotNull(createApiKeyResponse6.getKey()); - InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(createApiKeyResponse6.getId()); + InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(createApiKeyResponse6.getId(), false); ActionListener listener; // tag::invalidate-api-key-execute-listener @@ -2195,5 +2208,29 @@ public void onFailure(Exception e) { assertThat(invalidatedApiKeyIds, containsInAnyOrder(expectedInvalidatedApiKeyIds.toArray(Strings.EMPTY_ARRAY))); assertThat(response.getPreviouslyInvalidatedApiKeys().size(), equalTo(0)); } + + { + createApiKeyRequest = new CreateApiKeyRequest("k7", roles, expiration, refreshPolicy); + CreateApiKeyResponse createApiKeyResponse7 = client.security().createApiKey(createApiKeyRequest, RequestOptions.DEFAULT); + assertThat(createApiKeyResponse7.getName(), equalTo("k7")); + assertNotNull(createApiKeyResponse7.getKey()); + + // tag::invalidate-api-keys-owned-by-authenticated-user-request + InvalidateApiKeyRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.forOwnedApiKeys(); + // end::invalidate-api-keys-owned-by-authenticated-user-request + + InvalidateApiKeyResponse invalidateApiKeyResponse = client.security().invalidateApiKey(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(createApiKeyResponse7.getId()); + assertThat(invalidatedApiKeyIds, containsInAnyOrder(expectedInvalidatedApiKeyIds.toArray(Strings.EMPTY_ARRAY))); + assertThat(previouslyInvalidatedApiKeyIds.size(), equalTo(0)); + } + } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetApiKeyRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetApiKeyRequestTests.java index 79551e1e73e92..cbd05ae4c5ac3 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetApiKeyRequestTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetApiKeyRequestTests.java @@ -30,10 +30,10 @@ public class GetApiKeyRequestTests extends ESTestCase { public void testRequestValidation() { - GetApiKeyRequest request = GetApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5)); + GetApiKeyRequest request = GetApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5), randomBoolean()); Optional ve = request.validate(); assertFalse(ve.isPresent()); - request = GetApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5)); + request = GetApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5), randomBoolean()); ve = request.validate(); assertFalse(ve.isPresent()); request = GetApiKeyRequest.usingRealmName(randomAlphaOfLength(5)); @@ -45,28 +45,40 @@ public void testRequestValidation() { request = GetApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(5), randomAlphaOfLength(7)); ve = request.validate(); assertFalse(ve.isPresent()); + request = GetApiKeyRequest.forOwnedApiKeys(); + ve = request.validate(); + assertFalse(ve.isPresent()); } public void testRequestValidationFailureScenarios() throws IOException { String[][] inputs = new String[][] { - { randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), - randomFrom(new String[] { null, "" }) }, - { randomFrom(new String[] { null, "" }), "user", "api-kid", "api-kname" }, - { "realm", randomFrom(new String[] { null, "" }), "api-kid", "api-kname" }, - { "realm", "user", "api-kid", randomFrom(new String[] { null, "" }) }, - { randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), "api-kid", "api-kname" } }; - String[] expectedErrorMessages = new String[] { "One of [api key id, api key name, username, realm name] must be specified", + { randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "false" }, + { randomNullOrEmptyString(), "user", "api-kid", "api-kname", "false" }, + { "realm", randomNullOrEmptyString(), "api-kid", "api-kname", "false" }, + { "realm", "user", "api-kid", randomNullOrEmptyString(), "false" }, + { randomNullOrEmptyString(), randomNullOrEmptyString(), "api-kid", "api-kname", "false" }, + { "realm", randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "true"}, + { randomNullOrEmptyString(), "user", randomNullOrEmptyString(), randomNullOrEmptyString(), "true"} }; + String[] expectedErrorMessages = new String[] { + "One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false", "username or realm name must not be specified when the api key id or api key name is specified", "username or realm name must not be specified when the api key id or api key name is specified", "username or realm name must not be specified when the api key id or api key name is specified", - "only one of [api key id, api key name] can be specified" }; + "only one of [api key id, api key name] can be specified", + "neither username nor realm-name may be specified when retrieving owned API keys", + "neither username nor realm-name may be specified when retrieving owned API keys" }; for (int i = 0; i < inputs.length; i++) { final int caseNo = i; IllegalArgumentException ve = expectThrows(IllegalArgumentException.class, - () -> new GetApiKeyRequest(inputs[caseNo][0], inputs[caseNo][1], inputs[caseNo][2], inputs[caseNo][3])); + () -> new GetApiKeyRequest(inputs[caseNo][0], inputs[caseNo][1], inputs[caseNo][2], inputs[caseNo][3], + Boolean.valueOf(inputs[caseNo][4]))); assertNotNull(ve); assertThat(ve.getMessage(), equalTo(expectedErrorMessages[caseNo])); } } + + private static String randomNullOrEmptyString() { + return randomBoolean() ? "" : null; + } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateApiKeyRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateApiKeyRequestTests.java index 25ee4bb05bcc4..a29adb9ea382b 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateApiKeyRequestTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/InvalidateApiKeyRequestTests.java @@ -31,10 +31,10 @@ public class InvalidateApiKeyRequestTests extends ESTestCase { public void testRequestValidation() { - InvalidateApiKeyRequest request = InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5)); + InvalidateApiKeyRequest request = InvalidateApiKeyRequest.usingApiKeyId(randomAlphaOfLength(5), randomBoolean()); Optional ve = request.validate(); assertThat(ve.isPresent(), is(false)); - request = InvalidateApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5)); + request = InvalidateApiKeyRequest.usingApiKeyName(randomAlphaOfLength(5), randomBoolean()); ve = request.validate(); assertThat(ve.isPresent(), is(false)); request = InvalidateApiKeyRequest.usingRealmName(randomAlphaOfLength(5)); @@ -46,28 +46,40 @@ public void testRequestValidation() { request = InvalidateApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(5), randomAlphaOfLength(7)); ve = request.validate(); assertThat(ve.isPresent(), is(false)); + request = InvalidateApiKeyRequest.forOwnedApiKeys(); + ve = request.validate(); + assertFalse(ve.isPresent()); } public void testRequestValidationFailureScenarios() throws IOException { String[][] inputs = new String[][] { - { randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), - randomFrom(new String[] { null, "" }) }, - { randomFrom(new String[] { null, "" }), "user", "api-kid", "api-kname" }, - { "realm", randomFrom(new String[] { null, "" }), "api-kid", "api-kname" }, - { "realm", "user", "api-kid", randomFrom(new String[] { null, "" }) }, - { randomFrom(new String[] { null, "" }), randomFrom(new String[] { null, "" }), "api-kid", "api-kname" } }; - String[] expectedErrorMessages = new String[] { "One of [api key id, api key name, username, realm name] must be specified", + { randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "false" }, + { randomNullOrEmptyString(), "user", "api-kid", "api-kname", "false" }, + { "realm", randomNullOrEmptyString(), "api-kid", "api-kname", "false" }, + { "realm", "user", "api-kid", randomNullOrEmptyString(), "false" }, + { randomNullOrEmptyString(), randomNullOrEmptyString(), "api-kid", "api-kname", "false" }, + { "realm", randomNullOrEmptyString(), randomNullOrEmptyString(), randomNullOrEmptyString(), "true" }, + { randomNullOrEmptyString(), "user", randomNullOrEmptyString(), randomNullOrEmptyString(), "true" } }; + String[] expectedErrorMessages = new String[] { + "One of [api key id, api key name, username, realm name] must be specified if [owner] flag is false", "username or realm name must not be specified when the api key id or api key name is specified", "username or realm name must not be specified when the api key id or api key name is specified", "username or realm name must not be specified when the api key id or api key name is specified", - "only one of [api key id, api key name] can be specified" }; + "only one of [api key id, api key name] can be specified", + "neither username nor realm-name may be specified when invalidating owned API keys", + "neither username nor realm-name may be specified when invalidating owned API keys" }; for (int i = 0; i < inputs.length; i++) { final int caseNo = i; IllegalArgumentException ve = expectThrows(IllegalArgumentException.class, - () -> new InvalidateApiKeyRequest(inputs[caseNo][0], inputs[caseNo][1], inputs[caseNo][2], inputs[caseNo][3])); + () -> new InvalidateApiKeyRequest(inputs[caseNo][0], inputs[caseNo][1], inputs[caseNo][2], inputs[caseNo][3], + Boolean.valueOf(inputs[caseNo][4]))); assertNotNull(ve); assertThat(ve.getMessage(), equalTo(expectedErrorMessages[caseNo])); } } + + private static String randomNullOrEmptyString() { + return randomBoolean() ? "" : null; + } } diff --git a/docs/java-rest/high-level/security/get-api-key.asciidoc b/docs/java-rest/high-level/security/get-api-key.asciidoc index bb98b527d22ba..911acd3e92ef5 100644 --- a/docs/java-rest/high-level/security/get-api-key.asciidoc +++ b/docs/java-rest/high-level/security/get-api-key.asciidoc @@ -21,6 +21,8 @@ The +{request}+ supports retrieving API key information for . All API keys for a specific user in a specific realm +. A specific key or all API keys owned by the current authenticated user + ===== Retrieve a specific API key by its id ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- @@ -51,6 +53,12 @@ include-tagged::{doc-tests-file}[get-user-api-keys-request] include-tagged::{doc-tests-file}[get-user-realm-api-keys-request] -------------------------------------------------- +===== Retrieve all API keys for the current authenticated user +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[get-api-keys-owned-by-authenticated-user-request] +-------------------------------------------------- + include::../execution.asciidoc[] [id="{upid}-{api}-response"] diff --git a/docs/java-rest/high-level/security/invalidate-api-key.asciidoc b/docs/java-rest/high-level/security/invalidate-api-key.asciidoc index 7f9c43b3165a8..b8a99f932d93e 100644 --- a/docs/java-rest/high-level/security/invalidate-api-key.asciidoc +++ b/docs/java-rest/high-level/security/invalidate-api-key.asciidoc @@ -21,6 +21,8 @@ The +{request}+ supports invalidating . All API keys for a specific user in a specific realm +. A specific key or all API keys owned by the current authenticated user + ===== Specific API key by API key id ["source","java",subs="attributes,callouts,macros"] -------------------------------------------------- @@ -51,6 +53,12 @@ include-tagged::{doc-tests-file}[invalidate-user-api-keys-request] include-tagged::{doc-tests-file}[invalidate-user-realm-api-keys-request] -------------------------------------------------- +===== Retrieve all API keys for the current authenticated user +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[invalidate-api-keys-owned-by-authenticated-user-request] +-------------------------------------------------- + include::../execution.asciidoc[] [id="{upid}-{api}-response"] From 4d1bed01e8764141ddd210f62cc8b331a174e4ff Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad <902768+bizybot@users.noreply.github.com> Date: Fri, 16 Aug 2019 07:50:53 +1000 Subject: [PATCH 05/11] Allow API key to retrieve its own information with no API key privilege (#45433) Unless the API key has `manage_api_key` privilege, it cannot get its own API key information when authenticating using an API key. There can be a use case wherein we do not wish the user authenticating using an API key to be able to invalidate or view any other API keys but only view information about itself. This commit addresses this by allowing the request when API key id from the `GetApiKeyRequest` matches the API key id present in the `authentication` metadata. Relates: #40031 --- .../xpack/security/authc/ApiKeyService.java | 2 + .../security/authc/AuthenticationService.java | 2 +- .../xpack/security/authz/RBACEngine.java | 54 ++++++++++++------- .../security/authc/ApiKeyIntegTests.java | 27 ++++++++-- .../xpack/security/authz/RBACEngineTests.java | 51 ++++++++++++++++++ 5 files changed, 113 insertions(+), 23 deletions(-) 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 ec7c2b7870840..b8777b62f1f23 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 @@ -107,6 +107,8 @@ public class ApiKeyService { private static final Logger logger = LogManager.getLogger(ApiKeyService.class); private static final DeprecationLogger deprecationLogger = new DeprecationLogger(logger); public static final String API_KEY_ID_KEY = "_security_api_key_id"; + public static final String API_KEY_REALM_NAME = "_es_api_key"; + public static final String API_KEY_REALM_TYPE = "_es_api_key"; static final String API_KEY_ROLE_DESCRIPTORS_KEY = "_security_api_key_role_descriptors"; static final String API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY = "_security_api_key_limited_by_role_descriptors"; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java index 1fe3ed67f7337..1d54c3e8dddd2 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/AuthenticationService.java @@ -269,7 +269,7 @@ private void checkForApiKey() { apiKeyService.authenticateWithApiKeyIfPresent(threadContext, ActionListener.wrap(authResult -> { if (authResult.isAuthenticated()) { final User user = authResult.getUser(); - authenticatedBy = new RealmRef("_es_api_key", "_es_api_key", nodeName); + authenticatedBy = new RealmRef(ApiKeyService.API_KEY_REALM_NAME, ApiKeyService.API_KEY_REALM_TYPE, nodeName); writeAuthToContext(new Authentication(user, authenticatedBy, null, Version.CURRENT, Authentication.AuthenticationType.API_KEY, authResult.getMetadata())); } else if (authResult.getStatus() == AuthenticationResult.Status.TERMINATE) { 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 df00474f6d69d..a17cca5283211 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 @@ -32,6 +32,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.GetApiKeyAction; +import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest; 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; @@ -62,6 +64,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; import org.elasticsearch.xpack.core.security.support.Automatons; import org.elasticsearch.xpack.core.security.user.User; +import org.elasticsearch.xpack.security.authc.ApiKeyService; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; @@ -86,7 +89,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, GetApiKeyAction.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]"; @@ -154,26 +157,39 @@ 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 GetApiKeyRequest) { + GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request; + if (authentication.getAuthenticatedBy().getType().equals(ApiKeyService.API_KEY_REALM_TYPE)) { + assert authentication.getLookedUpBy() == null : "runAs not supported for api key authentication"; + // if authenticated by API key then the request must also contain same API key id + String authenticatedApiKeyId = (String) authentication.getMetadata().get(ApiKeyService.API_KEY_ID_KEY); + if (Strings.hasText(getApiKeyRequest.getApiKeyId())) { + return getApiKeyRequest.getApiKeyId().equals(authenticatedApiKeyId); + } else { + return false; + } + } + } else { + assert false : "right now only a user request or get 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/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 f73719cf25937..bec82b17c1495 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 @@ -47,6 +47,7 @@ import java.util.Base64; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -532,9 +533,25 @@ public void testInvalidateApiKeysOwnedByCurrentAuthenticatedUser() throws Interr verifyInvalidateResponse(noOfApiKeysForUserWithManageApiKeyRole, userWithManageApiKeyRoleApiKeys, invalidateResponse); } - private void verifyGetResponse(int expectedNumberOfApiKeys, List responses, GetApiKeyResponse response, - Set validApiKeyIds, - List invalidatedApiKeyIds) { + public void testApiKeyAuthorizationApiKeyMustBeAbleToRetrieveItsOwnInformation() throws InterruptedException, ExecutionException { + List responses = createApiKeys(2, null); + final String base64ApiKeyKeyValue = Base64.getEncoder().encodeToString( + (responses.get(0).getId() + ":" + responses.get(0).getKey().toString()).getBytes(StandardCharsets.UTF_8)); + Client client = client().filterWithHeader(Map.of("Authorization", "ApiKey " + base64ApiKeyKeyValue)); + PlainActionFuture listener = new PlainActionFuture<>(); + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(responses.get(0).getId(), false), listener); + GetApiKeyResponse response = listener.get(); + verifyGetResponse(1, responses, response, Collections.singleton(responses.get(0).getId()), null); + + final PlainActionFuture failureListener = new PlainActionFuture<>(); + // for any other API key id, it must deny access + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(responses.get(1).getId(), false), failureListener); + ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, () -> failureListener.actionGet()); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", SecuritySettingsSource.TEST_SUPERUSER); + } + + private void verifyGetResponse(int expectedNumberOfApiKeys, List responses, + GetApiKeyResponse response, Set validApiKeyIds, List invalidatedApiKeyIds) { verifyGetResponse(SecuritySettingsSource.TEST_SUPERUSER, expectedNumberOfApiKeys, responses, response, validApiKeyIds, invalidatedApiKeyIds); } @@ -584,4 +601,8 @@ private List createApiKeys(String user, int noOfApiKeys, T assertThat(responses.size(), is(noOfApiKeys)); return responses; } + + private void assertErrorMessage(final ElasticsearchSecurityException ese, String action, String userName) { + assertThat(ese.getMessage(), is("action [" + action + "] is unauthorized for user [" + userName + "]")); + } } 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 0a431ec95f5ee..fd84afea365be 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,8 @@ import org.elasticsearch.license.GetLicenseAction; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.action.GetApiKeyAction; +import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest; 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; @@ -51,6 +53,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.Privilege; import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; import org.elasticsearch.xpack.core.security.user.User; +import org.elasticsearch.xpack.security.authc.ApiKeyService; import org.elasticsearch.xpack.security.authc.esnative.ReservedRealm; import org.elasticsearch.xpack.security.authz.RBACEngine.RBACAuthorizationInfo; import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; @@ -63,6 +66,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Set; import static java.util.Collections.emptyMap; @@ -232,6 +236,53 @@ public void testSameUserPermissionDoesNotAllowChangePasswordForLookedUpByOtherRe verifyNoMoreInteractions(authentication, lookedUpBy, authenticatedBy); } + public void testSameUserPermissionAllowsSelfApiKeyInfoRetrievalWhenAuthenticatedByApiKey() { + final User user = new User("joe"); + final String apiKeyId = randomAlphaOfLengthBetween(4, 7); + final TransportRequest request = GetApiKeyRequest.usingApiKeyId(apiKeyId, false); + 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(ApiKeyService.API_KEY_REALM_TYPE); + when(authentication.getMetadata()).thenReturn(Map.of(ApiKeyService.API_KEY_ID_KEY, apiKeyId)); + + assertTrue(engine.checkSameUserPermissions(GetApiKeyAction.NAME, request, authentication)); + } + + public void testSameUserPermissionDeniesApiKeyInfoRetrievalWhenAuthenticatedByADifferentApiKey() { + final User user = new User("joe"); + final String apiKeyId = randomAlphaOfLengthBetween(4, 7); + final TransportRequest request = GetApiKeyRequest.usingApiKeyId(apiKeyId, false); + 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(ApiKeyService.API_KEY_REALM_TYPE); + when(authentication.getMetadata()).thenReturn(Map.of(ApiKeyService.API_KEY_ID_KEY, randomAlphaOfLengthBetween(4, 7))); + + assertFalse(engine.checkSameUserPermissions(GetApiKeyAction.NAME, request, authentication)); + } + + public void testSameUserPermissionDeniesApiKeyInfoRetrievalWhenLookedupByIsPresent() { + final User user = new User("joe"); + final String apiKeyId = randomAlphaOfLengthBetween(4, 7); + final TransportRequest request = GetApiKeyRequest.usingApiKeyId(apiKeyId, false); + final Authentication authentication = mock(Authentication.class); + final Authentication.RealmRef authenticatedBy = mock(Authentication.RealmRef.class); + final Authentication.RealmRef lookedupBy = mock(Authentication.RealmRef.class); + when(authentication.getUser()).thenReturn(user); + when(authentication.getAuthenticatedBy()).thenReturn(authenticatedBy); + when(authentication.getLookedUpBy()).thenReturn(lookedupBy); + when(authenticatedBy.getType()).thenReturn(ApiKeyService.API_KEY_REALM_TYPE); + when(authentication.getMetadata()).thenReturn(Map.of(ApiKeyService.API_KEY_ID_KEY, randomAlphaOfLengthBetween(4, 7))); + + final AssertionError assertionError = expectThrows(AssertionError.class, () -> engine.checkSameUserPermissions(GetApiKeyAction.NAME, + request, authentication)); + assertNotNull(assertionError); + assertThat(assertionError.getLocalizedMessage(), is("runAs not supported for api key authentication")); + } + /** * This tests that action names in the request are considered "matched" by the relevant named privilege * (in this case that {@link DeleteAction} and {@link IndexAction} are satisfied by {@link IndexPrivilege#WRITE}). From 5661e98e5ea5e451376903f525c41e0053348885 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad <902768+bizybot@users.noreply.github.com> Date: Thu, 22 Aug 2019 16:37:12 +1000 Subject: [PATCH 06/11] Add support for authentication based predicate for cluster permission (#45431) Currently, cluster permission checks whether a cluster action is permitted and optionally in the context of a request. There are scenarios where we would want to check whether the cluster action is permitted, optionally in the context of a request and current authentication. For example, management of API keys is only restricted to the API keys owned by the current user. In this case, along with the cluster action and API key request, the check needs to perform whether the currently authenticated user is indeed allowed to operate only on owned API keys. With this commit, we are introducing one more context of the current authentication that can be considered during permission evaluation. Relates: #40031 --- .../authz/permission/ClusterPermission.java | 77 ++- .../authz/permission/LimitedRole.java | 10 +- .../core/security/authz/permission/Role.java | 9 +- .../ConfigurableClusterPrivileges.java | 5 +- .../permission/ClusterPermissionTests.java | 45 +- .../authz/permission/LimitedRoleTests.java | 25 +- .../ManageApplicationPrivilegesTests.java | 20 +- .../authz/privilege/PrivilegeTests.java | 7 +- .../authz/store/ReservedRolesStoreTests.java | 564 +++++++++--------- .../xpack/security/authz/RBACEngine.java | 2 +- .../authc/esnative/NativeRealmIntegTests.java | 6 +- .../authz/AuthorizationServiceTests.java | 11 +- .../authz/store/CompositeRolesStoreTests.java | 26 +- .../authz/store/FileRolesStoreTests.java | 6 +- 14 files changed, 436 insertions(+), 377 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java index 6d6a01684760c..9d32ee9909c75 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java @@ -8,8 +8,8 @@ import org.apache.lucene.util.automaton.Automaton; import org.apache.lucene.util.automaton.Operations; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege; -import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege; import org.elasticsearch.xpack.core.security.support.Automatons; import java.util.ArrayList; @@ -34,14 +34,16 @@ private ClusterPermission(final Set clusterPrivileges, } /** - * Checks permission to a cluster action for a given request. + * Checks permission to a cluster action for a given request in the context of given + * authentication. * * @param action cluster action * @param request {@link TransportRequest} + * @param authentication {@link Authentication} * @return {@code true} if the access is allowed else returns {@code false} */ - public boolean check(final String action, final TransportRequest request) { - return checks.stream().anyMatch(permission -> permission.check(action, request)); + public boolean check(final String action, final TransportRequest request, final Authentication authentication) { + return checks.stream().anyMatch(permission -> permission.check(action, request, authentication)); } /** @@ -80,21 +82,15 @@ public static class Builder { public Builder add(final ClusterPrivilege clusterPrivilege, final Set allowedActionPatterns, final Set excludeActionPatterns) { this.clusterPrivileges.add(clusterPrivilege); - if (allowedActionPatterns.isEmpty() && excludeActionPatterns.isEmpty()) { - this.actionAutomatons.add(Automatons.EMPTY); - } else { - final Automaton allowedAutomaton = Automatons.patterns(allowedActionPatterns); - final Automaton excludedAutomaton = Automatons.patterns(excludeActionPatterns); - this.actionAutomatons.add(Automatons.minusAndMinimize(allowedAutomaton, excludedAutomaton)); - } + final Automaton actionAutomaton = createAutomaton(allowedActionPatterns, excludeActionPatterns); + this.actionAutomatons.add(actionAutomaton); return this; } - public Builder add(final ConfigurableClusterPrivilege configurableClusterPrivilege, final Predicate actionPredicate, + public Builder add(final ClusterPrivilege clusterPrivilege, final Set allowedActionPatterns, final Predicate requestPredicate) { - return add(configurableClusterPrivilege, new ActionRequestPredicatePermissionCheck(configurableClusterPrivilege, - actionPredicate, - requestPredicate)); + final Automaton actionAutomaton = createAutomaton(allowedActionPatterns, Set.of()); + return add(clusterPrivilege, new ActionRequestBasedPermissionCheck(clusterPrivilege, actionAutomaton, requestPredicate)); } public Builder add(final ClusterPrivilege clusterPrivilege, final PermissionCheck permissionCheck) { @@ -116,6 +112,21 @@ public ClusterPermission build() { } return new ClusterPermission(this.clusterPrivileges, checks); } + + private static Automaton createAutomaton(Set allowedActionPatterns, Set excludeActionPatterns) { + allowedActionPatterns = (allowedActionPatterns == null) ? Set.of() : allowedActionPatterns; + excludeActionPatterns = (excludeActionPatterns == null) ? Set.of() : excludeActionPatterns; + + if (allowedActionPatterns.isEmpty()) { + return Automatons.EMPTY; + } else if (excludeActionPatterns.isEmpty()) { + return Automatons.patterns(allowedActionPatterns); + } else { + final Automaton allowedAutomaton = Automatons.patterns(allowedActionPatterns); + final Automaton excludedAutomaton = Automatons.patterns(excludeActionPatterns); + return Automatons.minusAndMinimize(allowedAutomaton, excludedAutomaton); + } + } } /** @@ -124,13 +135,15 @@ public ClusterPermission build() { */ public interface PermissionCheck { /** - * Checks permission to a cluster action for a given request. + * Checks permission to a cluster action for a given request in the context of given + * authentication. * * @param action action name * @param request {@link TransportRequest} + * @param authentication {@link Authentication} * @return {@code true} if the specified action for given request is allowed else returns {@code false} */ - boolean check(String action, TransportRequest request); + boolean check(String action, TransportRequest request, Authentication authentication); /** * Checks whether specified {@link PermissionCheck} is implied by this {@link PermissionCheck}.
@@ -156,7 +169,7 @@ private static class AutomatonPermissionCheck implements PermissionCheck { } @Override - public boolean check(final String action, final TransportRequest request) { + public boolean check(final String action, final TransportRequest request, final Authentication authentication) { return actionPredicate.test(action); } @@ -169,29 +182,31 @@ public boolean implies(final PermissionCheck permissionCheck) { } } - // action and request based permission check - private static class ActionRequestPredicatePermissionCheck implements PermissionCheck { + // action, request based permission check + private static class ActionRequestBasedPermissionCheck extends AutomatonPermissionCheck { private final ClusterPrivilege clusterPrivilege; - final Predicate actionPredicate; - final Predicate requestPredicate; + private final Predicate requestPredicate; - ActionRequestPredicatePermissionCheck(final ClusterPrivilege clusterPrivilege, final Predicate actionPredicate, - final Predicate requestPredicate) { - this.clusterPrivilege = clusterPrivilege; - this.actionPredicate = actionPredicate; + ActionRequestBasedPermissionCheck(ClusterPrivilege clusterPrivilege, final Automaton automaton, + final Predicate requestPredicate) { + super(automaton); this.requestPredicate = requestPredicate; + this.clusterPrivilege = clusterPrivilege; } @Override - public boolean check(final String action, final TransportRequest request) { - return actionPredicate.test(action) && requestPredicate.test(request); + public boolean check(final String action, final TransportRequest request, final Authentication authentication) { + return super.check(action, request, authentication) && requestPredicate.test(request); } @Override public boolean implies(final PermissionCheck permissionCheck) { - if (permissionCheck instanceof ActionRequestPredicatePermissionCheck) { - final ActionRequestPredicatePermissionCheck otherCheck = (ActionRequestPredicatePermissionCheck) permissionCheck; - return this.clusterPrivilege.equals(otherCheck.clusterPrivilege); + if (super.implies(permissionCheck)) { + if (permissionCheck instanceof ActionRequestBasedPermissionCheck) { + final ActionRequestBasedPermissionCheck otherCheck = + (ActionRequestBasedPermissionCheck) permissionCheck; + return this.clusterPrivilege.equals(otherCheck.clusterPrivilege); + } } return false; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java index 8c7491d0a9a3d..871be8cbc6569 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRole.java @@ -9,6 +9,7 @@ import org.apache.lucene.util.automaton.Automaton; import org.elasticsearch.cluster.metadata.AliasOrIndex; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor; import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege; @@ -122,15 +123,18 @@ public ResourcePrivilegesMap checkIndicesPrivileges(Set checkForIndexPat } /** - * Check if cluster permissions allow for the given action, also checks whether the limited by role allows the given actions + * Check if cluster permissions allow for the given action, + * also checks whether the limited by role allows the given actions in the context of given + * authentication. * * @param action cluster action * @param request {@link TransportRequest} + * @param authentication {@link Authentication} * @return {@code true} if action is allowed else returns {@code false} */ @Override - public boolean checkClusterAction(String action, TransportRequest request) { - return super.checkClusterAction(action, request) && limitedBy.checkClusterAction(action, request); + public boolean checkClusterAction(String action, TransportRequest request, Authentication authentication) { + return super.checkClusterAction(action, request, authentication) && limitedBy.checkClusterAction(action, request, authentication); } /** diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java index ef898a0876dda..94d583f616787 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/Role.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege; @@ -121,14 +122,16 @@ public ResourcePrivilegesMap checkIndicesPrivileges(Set checkForIndexPat } /** - * Check if cluster permissions allow for the given action + * Check if cluster permissions allow for the given action in the context of given + * authentication. * * @param action cluster action * @param request {@link TransportRequest} + * @param authentication {@link Authentication} * @return {@code true} if action is allowed else returns {@code false} */ - public boolean checkClusterAction(String action, TransportRequest request) { - return cluster.check(action, request); + public boolean checkClusterAction(String action, TransportRequest request, Authentication authentication) { + return cluster.check(action, request, authentication); } /** diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConfigurableClusterPrivileges.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConfigurableClusterPrivileges.java index 22ba4c1f2e33a..a5ec573d5c988 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConfigurableClusterPrivileges.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ConfigurableClusterPrivileges.java @@ -125,8 +125,6 @@ private static void expectFieldName(XContentParser parser, ParseField... fields) * of applications (identified by a wildcard-aware application-name). */ public static class ManageApplicationPrivileges implements ConfigurableClusterPrivilege { - - private static final Predicate ACTION_PREDICATE = Automatons.predicate("cluster:admin/xpack/security/privilege/*"); public static final String WRITEABLE_NAME = "manage-application-privileges"; private final Set applicationNames; @@ -145,6 +143,7 @@ public ManageApplicationPrivileges(Set applicationNames) { } return false; }; + } @Override @@ -215,7 +214,7 @@ public int hashCode() { @Override public ClusterPermission.Builder buildPermission(final ClusterPermission.Builder builder) { - return builder.add(this, ACTION_PREDICATE, requestPredicate); + return builder.add(this, Set.of("cluster:admin/xpack/security/privilege/*"), requestPredicate); } private interface Fields { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermissionTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermissionTests.java index 18eb99e97f2f7..fe08db0d2e2e0 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermissionTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermissionTests.java @@ -12,12 +12,11 @@ import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilegeResolver; import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege; -import org.elasticsearch.xpack.core.security.support.Automatons; import org.junit.Before; -import org.mockito.Mockito; import java.io.IOException; import java.util.Objects; @@ -26,9 +25,11 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; +import static org.mockito.Mockito.mock; public class ClusterPermissionTests extends ESTestCase { - private TransportRequest mockTransportRequest = Mockito.mock(TransportRequest.class); + private TransportRequest mockTransportRequest; + private Authentication mockAuthentication; private ClusterPrivilege cpThatDoesNothing = new ClusterPrivilege() { @Override public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) { @@ -38,7 +39,8 @@ public ClusterPermission.Builder buildPermission(ClusterPermission.Builder build @Before public void setup() { - mockTransportRequest = Mockito.mock(TransportRequest.class); + mockTransportRequest = mock(TransportRequest.class); + mockAuthentication = mock(Authentication.class); } public void testClusterPermissionBuilder() { @@ -78,10 +80,12 @@ public void testClusterPermissionCheck() { builder = mockConfigurableClusterPrivilege2.buildPermission(builder); final ClusterPermission clusterPermission = builder.build(); - assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest), is(true)); - assertThat(clusterPermission.check("cluster:admin/ilm/stop", mockTransportRequest), is(true)); - assertThat(clusterPermission.check("cluster:admin/xpack/security/privilege/get", mockTransportRequest), is(true)); - assertThat(clusterPermission.check("cluster:admin/snapshot/status", mockTransportRequest), is(false)); + assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest, mockAuthentication), + is(true)); + assertThat(clusterPermission.check("cluster:admin/ilm/stop", mockTransportRequest, mockAuthentication), is(true)); + assertThat(clusterPermission.check("cluster:admin/xpack/security/privilege/get", mockTransportRequest, mockAuthentication), + is(true)); + assertThat(clusterPermission.check("cluster:admin/snapshot/status", mockTransportRequest, mockAuthentication), is(false)); } public void testClusterPermissionCheckWithEmptyActionPatterns() { @@ -89,8 +93,9 @@ public void testClusterPermissionCheckWithEmptyActionPatterns() { builder.add(cpThatDoesNothing, Set.of(), Set.of()); final ClusterPermission clusterPermission = builder.build(); - assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest), is(false)); - assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest), is(false)); + assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest, mockAuthentication), is(false)); + assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest, mockAuthentication), + is(false)); } public void testClusterPermissionCheckWithExcludeOnlyActionPatterns() { @@ -98,8 +103,9 @@ public void testClusterPermissionCheckWithExcludeOnlyActionPatterns() { builder.add(cpThatDoesNothing, Set.of(), Set.of("cluster:some/thing/to/exclude")); final ClusterPermission clusterPermission = builder.build(); - assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest), is(false)); - assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest), is(false)); + assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest, mockAuthentication), is(false)); + assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest, mockAuthentication), + is(false)); } public void testClusterPermissionCheckWithActionPatterns() { @@ -107,8 +113,9 @@ public void testClusterPermissionCheckWithActionPatterns() { builder.add(cpThatDoesNothing, Set.of("cluster:admin/*"), Set.of("cluster:admin/ilm/*")); final ClusterPermission clusterPermission = builder.build(); - assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest), is(false)); - assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest), is(true)); + assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest, mockAuthentication), is(false)); + assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest, mockAuthentication), + is(true)); } public void testClusterPermissionCheckWithActionPatternsAndNoExludePatterns() { @@ -116,8 +123,9 @@ public void testClusterPermissionCheckWithActionPatternsAndNoExludePatterns() { builder.add(cpThatDoesNothing, Set.of("cluster:admin/*"), Set.of()); final ClusterPermission clusterPermission = builder.build(); - assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest), is(true)); - assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest), is(true)); + assertThat(clusterPermission.check("cluster:admin/ilm/start", mockTransportRequest, mockAuthentication), is(true)); + assertThat(clusterPermission.check("cluster:admin/xpack/security/token/invalidate", mockTransportRequest, mockAuthentication), + is(true)); } public void testNoneClusterPermissionIsImpliedByNone() { @@ -223,7 +231,6 @@ public void testClusterPermissionSubsetIsImpliedByAllClusterPermission() { } private static class MockConfigurableClusterPrivilege implements ConfigurableClusterPrivilege { - static final Predicate ACTION_PREDICATE = Automatons.predicate("cluster:admin/xpack/security/privilege/*"); private Predicate requestPredicate; MockConfigurableClusterPrivilege(Predicate requestPredicate) { @@ -269,13 +276,13 @@ public int hashCode() { @Override public String toString() { return "MockConfigurableClusterPrivilege{" + - "requestPredicate=" + requestPredicate + + "requestAuthnPredicate=" + requestPredicate + '}'; } @Override public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) { - return builder.add(this, ACTION_PREDICATE, requestPredicate); + return builder.add(this, Set.of("cluster:admin/xpack/security/privilege/*"), requestPredicate); } } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java index 4bcc581d072b1..74e06d1cbce25 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/LimitedRoleTests.java @@ -18,6 +18,7 @@ import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilege; import org.elasticsearch.xpack.core.security.authz.privilege.ApplicationPrivilegeDescriptor; @@ -128,24 +129,26 @@ public void testAuthorize() { public void testCheckClusterAction() { Role fromRole = Role.builder("a-role").cluster(Collections.singleton("manage_security"), Collections.emptyList()) - .build(); - assertThat(fromRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class)), is(true)); + .build(); + Authentication authentication = mock(Authentication.class); + assertThat(fromRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), is(true)); { Role limitedByRole = Role.builder("limited-role") - .cluster(Collections.singleton("all"), Collections.emptyList()).build(); - assertThat(limitedByRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class)), is(true)); - assertThat(limitedByRole.checkClusterAction("cluster:other-action", mock(TransportRequest.class)), is(true)); + .cluster(Collections.singleton("all"), Collections.emptyList()).build(); + assertThat(limitedByRole.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), + is(true)); + assertThat(limitedByRole.checkClusterAction("cluster:other-action", mock(TransportRequest.class), authentication), is(true)); Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); - assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class)), is(true)); - assertThat(role.checkClusterAction("cluster:other-action", mock(TransportRequest.class)), is(false)); + assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), is(true)); + assertThat(role.checkClusterAction("cluster:other-action", mock(TransportRequest.class), authentication), is(false)); } { Role limitedByRole = Role.builder("limited-role") - .cluster(Collections.singleton("monitor"), Collections.emptyList()).build(); - assertThat(limitedByRole.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class)), is(true)); + .cluster(Collections.singleton("monitor"), Collections.emptyList()).build(); + assertThat(limitedByRole.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class), authentication), is(true)); Role role = LimitedRole.createLimitedRole(fromRole, limitedByRole); - assertThat(role.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class)), is(false)); - assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class)), is(false)); + assertThat(role.checkClusterAction("cluster:monitor/me", mock(TransportRequest.class), authentication), is(false)); + assertThat(role.checkClusterAction("cluster:admin/xpack/security/x", mock(TransportRequest.class), authentication), is(false)); } } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivilegesTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivilegesTests.java index dfe1147fb2c43..10eea045aadab 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivilegesTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageApplicationPrivilegesTests.java @@ -24,6 +24,7 @@ import org.elasticsearch.xpack.core.security.action.privilege.DeletePrivilegesRequest; import org.elasticsearch.xpack.core.security.action.privilege.GetPrivilegesRequest; import org.elasticsearch.xpack.core.security.action.privilege.PutPrivilegesRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission; import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivileges.ManageApplicationPrivileges; @@ -40,6 +41,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.nullValue; +import static org.mockito.Mockito.mock; public class ManageApplicationPrivilegesTests extends ESTestCase { @@ -97,14 +99,15 @@ public void testActionAndRequestPredicate() { assertThat(kibanaAndLogstashPermission, notNullValue()); assertThat(cloudAndSwiftypePermission, notNullValue()); + final Authentication authentication = mock(Authentication.class); final GetPrivilegesRequest getKibana1 = new GetPrivilegesRequest(); getKibana1.application("kibana-1"); - assertTrue(kibanaAndLogstashPermission.check("cluster:admin/xpack/security/privilege/get", getKibana1)); - assertFalse(cloudAndSwiftypePermission.check("cluster:admin/xpack/security/privilege/get", getKibana1)); + assertTrue(kibanaAndLogstashPermission.check("cluster:admin/xpack/security/privilege/get", getKibana1, authentication)); + assertFalse(cloudAndSwiftypePermission.check("cluster:admin/xpack/security/privilege/get", getKibana1, authentication)); final DeletePrivilegesRequest deleteLogstash = new DeletePrivilegesRequest("logstash", new String[]{"all"}); - assertTrue(kibanaAndLogstashPermission.check("cluster:admin/xpack/security/privilege/get", deleteLogstash)); - assertFalse(cloudAndSwiftypePermission.check("cluster:admin/xpack/security/privilege/get", deleteLogstash)); + assertTrue(kibanaAndLogstashPermission.check("cluster:admin/xpack/security/privilege/get", deleteLogstash, authentication)); + assertFalse(cloudAndSwiftypePermission.check("cluster:admin/xpack/security/privilege/get", deleteLogstash, authentication)); final PutPrivilegesRequest putKibana = new PutPrivilegesRequest(); @@ -114,11 +117,12 @@ public void testActionAndRequestPredicate() { randomAlphaOfLengthBetween(3, 6).toLowerCase(Locale.ROOT), Collections.emptySet(), Collections.emptyMap())); } putKibana.setPrivileges(kibanaPrivileges); - assertTrue(kibanaAndLogstashPermission.check("cluster:admin/xpack/security/privilege/get", putKibana)); - assertFalse(cloudAndSwiftypePermission.check("cluster:admin/xpack/security/privilege/get", putKibana)); + assertTrue(kibanaAndLogstashPermission.check("cluster:admin/xpack/security/privilege/get", putKibana, authentication)); + assertFalse(cloudAndSwiftypePermission.check("cluster:admin/xpack/security/privilege/get", putKibana, authentication)); } public void testSecurityForGetAllApplicationPrivileges() { + final Authentication authentication = mock(Authentication.class); final GetPrivilegesRequest getAll = new GetPrivilegesRequest(); getAll.application(null); getAll.privileges(new String[0]); @@ -130,8 +134,8 @@ public void testSecurityForGetAllApplicationPrivileges() { final ClusterPermission kibanaOnlyPermission = kibanaOnly.buildPermission(ClusterPermission.builder()).build(); final ClusterPermission allAppsPermission = allApps.buildPermission(ClusterPermission.builder()).build(); - assertFalse(kibanaOnlyPermission.check("cluster:admin/xpack/security/privilege/get", getAll)); - assertTrue(allAppsPermission.check("cluster:admin/xpack/security/privilege/get", getAll)); + assertFalse(kibanaOnlyPermission.check("cluster:admin/xpack/security/privilege/get", getAll, authentication)); + assertTrue(allAppsPermission.check("cluster:admin/xpack/security/privilege/get", getAll, authentication)); } private ManageApplicationPrivileges clone(ManageApplicationPrivileges original) { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java index bd64d2112287f..e02c930101694 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/PrivilegeTests.java @@ -9,17 +9,18 @@ import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission; import org.elasticsearch.xpack.core.security.support.Automatons; import org.junit.Rule; import org.junit.rules.ExpectedException; -import org.mockito.Mockito; import java.util.Set; import java.util.function.Predicate; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; +import static org.mockito.Mockito.mock; public class PrivilegeTests extends ESTestCase { @Rule @@ -35,13 +36,13 @@ public void testSubActionPattern() throws Exception { private void verifyClusterActionAllowed(ClusterPrivilege clusterPrivilege, String... actions) { ClusterPermission clusterPermission = clusterPrivilege.buildPermission(ClusterPermission.builder()).build(); for (String action: actions) { - assertTrue(clusterPermission.check(action, Mockito.mock(TransportRequest.class))); + assertTrue(clusterPermission.check(action, mock(TransportRequest.class), mock(Authentication.class))); } } private void verifyClusterActionDenied(ClusterPrivilege clusterPrivilege, String... actions) { ClusterPermission clusterPermission = clusterPrivilege.buildPermission(ClusterPermission.builder()).build(); for (String action: actions) { - assertFalse(clusterPermission.check(action, Mockito.mock(TransportRequest.class))); + assertFalse(clusterPermission.check(action, mock(TransportRequest.class), mock(Authentication.class))); } } public void testCluster() throws Exception { diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java index 4433b9d3750e7..4e6509d2ea823 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java @@ -124,6 +124,7 @@ import org.elasticsearch.xpack.core.security.action.token.CreateTokenAction; import org.elasticsearch.xpack.core.security.action.token.InvalidateTokenAction; import org.elasticsearch.xpack.core.security.action.user.PutUserAction; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.accesscontrol.IndicesAccessControl.IndexAccessControl; import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsCache; @@ -200,33 +201,34 @@ public void testIsReserved() { public void testSnapshotUserRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("snapshot_user"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role snapshotUserRole = Role.builder(roleDescriptor, null).build(); - assertThat(snapshotUserRole.cluster().check(GetRepositoriesAction.NAME, request), is(true)); - assertThat(snapshotUserRole.cluster().check(CreateSnapshotAction.NAME, request), is(true)); - assertThat(snapshotUserRole.cluster().check(SnapshotsStatusAction.NAME, request), is(true)); - assertThat(snapshotUserRole.cluster().check(GetSnapshotsAction.NAME, request), is(true)); - - assertThat(snapshotUserRole.cluster().check(PutRepositoryAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(DeleteIndexTemplateAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(PutPipelineAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(GetPipelineAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(DeletePipelineAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(GetWatchAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(PutWatchAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(DeleteWatchAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(ExecuteWatchAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(AckWatchAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(ActivateWatchAction.NAME, request), is(false)); - assertThat(snapshotUserRole.cluster().check(WatcherServiceAction.NAME, request), is(false)); + assertThat(snapshotUserRole.cluster().check(GetRepositoriesAction.NAME, request, authentication), is(true)); + assertThat(snapshotUserRole.cluster().check(CreateSnapshotAction.NAME, request, authentication), is(true)); + assertThat(snapshotUserRole.cluster().check(SnapshotsStatusAction.NAME, request, authentication), is(true)); + assertThat(snapshotUserRole.cluster().check(GetSnapshotsAction.NAME, request, authentication), is(true)); + + assertThat(snapshotUserRole.cluster().check(PutRepositoryAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(DeleteIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(PutPipelineAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(GetPipelineAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(DeletePipelineAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(GetWatchAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(PutWatchAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(DeleteWatchAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(ExecuteWatchAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(AckWatchAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(ActivateWatchAction.NAME, request, authentication), is(false)); + assertThat(snapshotUserRole.cluster().check(WatcherServiceAction.NAME, request, authentication), is(false)); assertThat(snapshotUserRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(randomAlphaOfLengthBetween(8, 24)), is(false)); assertThat(snapshotUserRole.indices().allowedIndicesMatcher("indices:foo").test(randomAlphaOfLengthBetween(8, 24)), is(false)); @@ -247,22 +249,23 @@ public void testSnapshotUserRole() { public void testIngestAdminRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("ingest_admin"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role ingestAdminRole = Role.builder(roleDescriptor, null).build(); - assertThat(ingestAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true)); - assertThat(ingestAdminRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(true)); - assertThat(ingestAdminRole.cluster().check(DeleteIndexTemplateAction.NAME, request), is(true)); - assertThat(ingestAdminRole.cluster().check(PutPipelineAction.NAME, request), is(true)); - assertThat(ingestAdminRole.cluster().check(GetPipelineAction.NAME, request), is(true)); - assertThat(ingestAdminRole.cluster().check(DeletePipelineAction.NAME, request), is(true)); + assertThat(ingestAdminRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); + assertThat(ingestAdminRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true)); + assertThat(ingestAdminRole.cluster().check(DeleteIndexTemplateAction.NAME, request, authentication), is(true)); + assertThat(ingestAdminRole.cluster().check(PutPipelineAction.NAME, request, authentication), is(true)); + assertThat(ingestAdminRole.cluster().check(GetPipelineAction.NAME, request, authentication), is(true)); + assertThat(ingestAdminRole.cluster().check(DeletePipelineAction.NAME, request, authentication), is(true)); - assertThat(ingestAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(ingestAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(ingestAdminRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(ingestAdminRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(ingestAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(ingestAdminRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(ingestAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); assertThat(ingestAdminRole.indices().allowedIndicesMatcher("indices:foo").test(randomAlphaOfLengthBetween(8, 24)), @@ -275,39 +278,40 @@ public void testIngestAdminRole() { public void testKibanaSystemRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_system"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role kibanaRole = Role.builder(roleDescriptor, null).build(); - assertThat(kibanaRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(ClusterStateAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(ClusterStatsAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(kibanaRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(kibanaRole.cluster().check(MonitoringBulkAction.NAME, request), is(true)); + assertThat(kibanaRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(kibanaRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(kibanaRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true)); // SAML and token - assertThat(kibanaRole.cluster().check(SamlPrepareAuthenticationAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(SamlAuthenticateAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(InvalidateTokenAction.NAME, request), is(true)); - assertThat(kibanaRole.cluster().check(CreateTokenAction.NAME, request), is(true)); + assertThat(kibanaRole.cluster().check(SamlPrepareAuthenticationAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(SamlAuthenticateAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(InvalidateTokenAction.NAME, request, authentication), is(true)); + assertThat(kibanaRole.cluster().check(CreateTokenAction.NAME, request, authentication), is(true)); // Application Privileges DeletePrivilegesRequest deleteKibanaPrivileges = new DeletePrivilegesRequest("kibana-.kibana", new String[]{ "all", "read" }); DeletePrivilegesRequest deleteLogstashPrivileges = new DeletePrivilegesRequest("logstash", new String[]{ "all", "read" }); - assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteKibanaPrivileges), is(true)); - assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteLogstashPrivileges), is(false)); + assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteKibanaPrivileges, authentication), is(true)); + assertThat(kibanaRole.cluster().check(DeletePrivilegesAction.NAME, deleteLogstashPrivileges, authentication), is(false)); GetPrivilegesRequest getKibanaPrivileges = new GetPrivilegesRequest(); getKibanaPrivileges.application("kibana-.kibana-sales"); GetPrivilegesRequest getApmPrivileges = new GetPrivilegesRequest(); getApmPrivileges.application("apm"); - assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getKibanaPrivileges), is(true)); - assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getApmPrivileges), is(false)); + assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getKibanaPrivileges, authentication), is(true)); + assertThat(kibanaRole.cluster().check(GetPrivilegesAction.NAME, getApmPrivileges, authentication), is(false)); PutPrivilegesRequest putKibanaPrivileges = new PutPrivilegesRequest(); putKibanaPrivileges.setPrivileges(Collections.singletonList(new ApplicationPrivilegeDescriptor( @@ -315,10 +319,10 @@ public void testKibanaSystemRole() { PutPrivilegesRequest putSwiftypePrivileges = new PutPrivilegesRequest(); putSwiftypePrivileges.setPrivileges(Collections.singletonList(new ApplicationPrivilegeDescriptor( "swiftype-kibana" , "all", Collections.emptySet(), Collections.emptyMap()))); - assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putKibanaPrivileges), is(true)); - assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putSwiftypePrivileges), is(false)); + assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putKibanaPrivileges, authentication), is(true)); + assertThat(kibanaRole.cluster().check(PutPrivilegesAction.NAME, putSwiftypePrivileges, authentication), is(false)); - assertThat(kibanaRole.cluster().check(GetBuiltinPrivilegesAction.NAME, request), is(true)); + assertThat(kibanaRole.cluster().check(GetBuiltinPrivilegesAction.NAME, request, authentication), is(true)); // Everything else assertThat(kibanaRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -378,19 +382,20 @@ public void testKibanaSystemRole() { public void testKibanaUserRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_user"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role kibanaUserRole = Role.builder(roleDescriptor, null).build(); - assertThat(kibanaUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false)); - assertThat(kibanaUserRole.cluster().check(ClusterStateAction.NAME, request), is(false)); - assertThat(kibanaUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false)); - assertThat(kibanaUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(kibanaUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(kibanaUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(kibanaUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(kibanaUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); + assertThat(kibanaUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false)); + assertThat(kibanaUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false)); + assertThat(kibanaUserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(kibanaUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(kibanaUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(kibanaUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(kibanaUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -415,21 +420,22 @@ public void testKibanaUserRole() { public void testMonitoringUserRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("monitoring_user"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role monitoringUserRole = Role.builder(roleDescriptor, null).build(); - assertThat(monitoringUserRole.cluster().check(MainAction.NAME, request), is(true)); - assertThat(monitoringUserRole.cluster().check(XPackInfoAction.NAME, request), is(true)); - assertThat(monitoringUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false)); - assertThat(monitoringUserRole.cluster().check(ClusterStateAction.NAME, request), is(false)); - assertThat(monitoringUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false)); - assertThat(monitoringUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(monitoringUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(monitoringUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(monitoringUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(monitoringUserRole.cluster().check(MainAction.NAME, request, authentication), is(true)); + assertThat(monitoringUserRole.cluster().check(XPackInfoAction.NAME, request, authentication), is(true)); + assertThat(monitoringUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); + assertThat(monitoringUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false)); + assertThat(monitoringUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false)); + assertThat(monitoringUserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(monitoringUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(monitoringUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(monitoringUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(monitoringUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -471,28 +477,29 @@ public void testMonitoringUserRole() { public void testRemoteMonitoringAgentRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("remote_monitoring_agent"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role remoteMonitoringAgentRole = Role.builder(roleDescriptor, null).build(); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(GetWatchAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(PutWatchAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(DeleteWatchAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(ExecuteWatchAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(AckWatchAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(ActivateWatchAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(WatcherServiceAction.NAME, request), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(GetWatchAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(PutWatchAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(DeleteWatchAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ExecuteWatchAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(AckWatchAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ActivateWatchAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(WatcherServiceAction.NAME, request, authentication), is(false)); // we get this from the cluster:monitor privilege - assertThat(remoteMonitoringAgentRole.cluster().check(WatcherStatsAction.NAME, request), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(WatcherStatsAction.NAME, request, authentication), is(true)); assertThat(remoteMonitoringAgentRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -530,21 +537,22 @@ public void testRemoteMonitoringAgentRole() { public void testRemoteMonitoringCollectorRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("remote_monitoring_collector"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role remoteMonitoringAgentRole = Role.builder(roleDescriptor, null).build(); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request), is(true)); - assertThat(remoteMonitoringAgentRole.cluster().check(GetIndexTemplatesAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(DeleteIndexTemplateAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(remoteMonitoringAgentRole.cluster().check(GetIndexTemplatesAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(DeleteIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(remoteMonitoringAgentRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(remoteMonitoringAgentRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -628,19 +636,20 @@ private void assertMonitoringOnRestrictedIndices(Role role) { public void testReportingUserRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("reporting_user"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role reportingUserRole = Role.builder(roleDescriptor, null).build(); - assertThat(reportingUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false)); - assertThat(reportingUserRole.cluster().check(ClusterStateAction.NAME, request), is(false)); - assertThat(reportingUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false)); - assertThat(reportingUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(reportingUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(reportingUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(reportingUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false)); + assertThat(reportingUserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(reportingUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(reportingUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(reportingUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -668,19 +677,20 @@ public void testReportingUserRole() { public void testKibanaDashboardOnlyUserRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("kibana_dashboard_only_user"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role dashboardsOnlyUserRole = Role.builder(roleDescriptor, null).build(); - assertThat(dashboardsOnlyUserRole.cluster().check(ClusterHealthAction.NAME, request), is(false)); - assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStateAction.NAME, request), is(false)); - assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStatsAction.NAME, request), is(false)); - assertThat(dashboardsOnlyUserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(dashboardsOnlyUserRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(dashboardsOnlyUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(dashboardsOnlyUserRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(dashboardsOnlyUserRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(dashboardsOnlyUserRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); @@ -702,18 +712,19 @@ public void testKibanaDashboardOnlyUserRole() { public void testSuperuserRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("superuser"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role superuserRole = Role.builder(roleDescriptor, null).build(); - assertThat(superuserRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(superuserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(true)); - assertThat(superuserRole.cluster().check(PutUserAction.NAME, request), is(true)); - assertThat(superuserRole.cluster().check(PutRoleAction.NAME, request), is(true)); - assertThat(superuserRole.cluster().check(PutIndexTemplateAction.NAME, request), is(true)); - assertThat(superuserRole.cluster().check("internal:admin/foo", request), is(false)); + assertThat(superuserRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(superuserRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(true)); + assertThat(superuserRole.cluster().check(PutUserAction.NAME, request, authentication), is(true)); + assertThat(superuserRole.cluster().check(PutRoleAction.NAME, request, authentication), is(true)); + assertThat(superuserRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(true)); + assertThat(superuserRole.cluster().check("internal:admin/foo", request, authentication), is(false)); final Settings indexSettings = Settings.builder().put("index.version.created", Version.CURRENT).build(); final String internalSecurityIndex = randomFrom(RestrictedIndicesNames.INTERNAL_SECURITY_MAIN_INDEX_6, @@ -772,19 +783,20 @@ public void testSuperuserRole() { public void testLogstashSystemRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("logstash_system"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role logstashSystemRole = Role.builder(roleDescriptor, null).build(); - assertThat(logstashSystemRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(logstashSystemRole.cluster().check(ClusterStateAction.NAME, request), is(true)); - assertThat(logstashSystemRole.cluster().check(ClusterStatsAction.NAME, request), is(true)); - assertThat(logstashSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(logstashSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(logstashSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(logstashSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true)); + assertThat(logstashSystemRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(logstashSystemRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(logstashSystemRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(logstashSystemRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(logstashSystemRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(logstashSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(logstashSystemRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true)); assertThat(logstashSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); @@ -798,6 +810,7 @@ public void testLogstashSystemRole() { public void testBeatsAdminRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); final RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("beats_admin"); assertNotNull(roleDescriptor); @@ -805,13 +818,13 @@ public void testBeatsAdminRole() { final Role beatsAdminRole = Role.builder(roleDescriptor, null).build(); - assertThat(beatsAdminRole.cluster().check(ClusterHealthAction.NAME, request), is(false)); - assertThat(beatsAdminRole.cluster().check(ClusterStateAction.NAME, request), is(false)); - assertThat(beatsAdminRole.cluster().check(ClusterStatsAction.NAME, request), is(false)); - assertThat(beatsAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(beatsAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(beatsAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(beatsAdminRole.cluster().check(MonitoringBulkAction.NAME, request), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(false)); + assertThat(beatsAdminRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(beatsAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(beatsAdminRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(beatsAdminRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); @@ -836,19 +849,20 @@ public void testBeatsAdminRole() { public void testBeatsSystemRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor(BeatsSystemUser.ROLE_NAME); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role beatsSystemRole = Role.builder(roleDescriptor, null).build(); - assertThat(beatsSystemRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(beatsSystemRole.cluster().check(ClusterStateAction.NAME, request), is(true)); - assertThat(beatsSystemRole.cluster().check(ClusterStatsAction.NAME, request), is(true)); - assertThat(beatsSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(beatsSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(beatsSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(beatsSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true)); + assertThat(beatsSystemRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(beatsSystemRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(beatsSystemRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(beatsSystemRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(beatsSystemRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(beatsSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(beatsSystemRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true)); assertThat(beatsSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); @@ -869,19 +883,20 @@ public void testBeatsSystemRole() { public void testAPMSystemRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor(APMSystemUser.ROLE_NAME); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role APMSystemRole = Role.builder(roleDescriptor, null).build(); - assertThat(APMSystemRole.cluster().check(ClusterHealthAction.NAME, request), is(true)); - assertThat(APMSystemRole.cluster().check(ClusterStateAction.NAME, request), is(true)); - assertThat(APMSystemRole.cluster().check(ClusterStatsAction.NAME, request), is(true)); - assertThat(APMSystemRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(APMSystemRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(APMSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); - assertThat(APMSystemRole.cluster().check(MonitoringBulkAction.NAME, request), is(true)); + assertThat(APMSystemRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(true)); + assertThat(APMSystemRole.cluster().check(ClusterStateAction.NAME, request, authentication), is(true)); + assertThat(APMSystemRole.cluster().check(ClusterStatsAction.NAME, request, authentication), is(true)); + assertThat(APMSystemRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(APMSystemRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(APMSystemRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(APMSystemRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(true)); assertThat(APMSystemRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); @@ -912,62 +927,63 @@ public void testAPMUserRole() { public void testMachineLearningAdminRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("machine_learning_admin"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(CloseJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteCalendarAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteFilterAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteForecastAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request), is(true)); - assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request), is(false)); // internal use only - assertThat(role.cluster().check(FindFileStructureAction.NAME, request), is(true)); - assertThat(role.cluster().check(FlushJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(ForecastJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetBucketsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetCalendarsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetCategoriesAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDatafeedsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetFiltersAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetInfluencersAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetJobsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetJobsStatsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetRecordsAction.NAME, request), is(true)); - assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request), is(false)); // internal use only - assertThat(role.cluster().check(KillProcessAction.NAME, request), is(false)); // internal use only - assertThat(role.cluster().check(MlInfoAction.NAME, request), is(true)); - assertThat(role.cluster().check(OpenJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(PersistJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request), is(true)); - assertThat(role.cluster().check(PostDataAction.NAME, request), is(true)); - assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request), is(true)); - assertThat(role.cluster().check(PutCalendarAction.NAME, request), is(true)); - assertThat(role.cluster().check(PutDatafeedAction.NAME, request), is(true)); - assertThat(role.cluster().check(PutFilterAction.NAME, request), is(true)); - assertThat(role.cluster().check(PutJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request), is(true)); - assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request), is(true)); - assertThat(role.cluster().check(StartDatafeedAction.NAME, request), is(true)); - assertThat(role.cluster().check(StopDatafeedAction.NAME, request), is(true)); - assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request), is(true)); - assertThat(role.cluster().check(UpdateFilterAction.NAME, request), is(true)); - assertThat(role.cluster().check(UpdateJobAction.NAME, request), is(true)); - assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request), is(true)); - assertThat(role.cluster().check(UpdateProcessAction.NAME, request), is(false)); // internal use only - assertThat(role.cluster().check(ValidateDetectorAction.NAME, request), is(true)); - assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request), is(true)); + assertThat(role.cluster().check(CloseJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteCalendarAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteFilterAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteForecastAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(FindFileStructureAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(FlushJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ForecastJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetBucketsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCalendarsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCategoriesAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDatafeedsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetFiltersAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetInfluencersAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetJobsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetJobsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetRecordsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(KillProcessAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(MlInfoAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(OpenJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PersistJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PostDataAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutCalendarAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutFilterAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(StartDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(StopDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateFilterAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateJobAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(UpdateProcessAction.NAME, request, authentication), is(false)); // internal use only + assertThat(role.cluster().check(ValidateDetectorAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request, authentication), is(true)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); assertNoAccessAllowed(role, "foo"); @@ -995,62 +1011,63 @@ public void testMachineLearningAdminRole() { public void testMachineLearningUserRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("machine_learning_user"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(CloseJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteCalendarAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteFilterAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteForecastAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request), is(false)); - assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request), is(false)); - assertThat(role.cluster().check(FindFileStructureAction.NAME, request), is(true)); - assertThat(role.cluster().check(FlushJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(ForecastJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(GetBucketsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetCalendarsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetCategoriesAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDatafeedsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetFiltersAction.NAME, request), is(false)); - assertThat(role.cluster().check(GetInfluencersAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetJobsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetJobsStatsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetRecordsAction.NAME, request), is(true)); - assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(KillProcessAction.NAME, request), is(false)); - assertThat(role.cluster().check(MlInfoAction.NAME, request), is(true)); - assertThat(role.cluster().check(OpenJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(PersistJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request), is(false)); - assertThat(role.cluster().check(PostDataAction.NAME, request), is(false)); - assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(PutCalendarAction.NAME, request), is(false)); - assertThat(role.cluster().check(PutDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(PutFilterAction.NAME, request), is(false)); - assertThat(role.cluster().check(PutJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request), is(false)); - assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request), is(false)); - assertThat(role.cluster().check(StartDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(StopDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request), is(false)); - assertThat(role.cluster().check(UpdateFilterAction.NAME, request), is(false)); - assertThat(role.cluster().check(UpdateJobAction.NAME, request), is(false)); - assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request), is(false)); - assertThat(role.cluster().check(UpdateProcessAction.NAME, request), is(false)); - assertThat(role.cluster().check(ValidateDetectorAction.NAME, request), is(false)); - assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request), is(false)); + assertThat(role.cluster().check(CloseJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteCalendarAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteCalendarEventAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteExpiredDataAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteFilterAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteForecastAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(DeleteModelSnapshotAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(FinalizeJobExecutionAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(FindFileStructureAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(FlushJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(ForecastJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(GetBucketsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCalendarEventsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCalendarsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetCategoriesAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDatafeedsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDatafeedsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetFiltersAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(GetInfluencersAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetJobsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetJobsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetModelSnapshotsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetOverallBucketsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetRecordsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(IsolateDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(KillProcessAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(MlInfoAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(OpenJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PersistJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PostCalendarEventsAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PostDataAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PreviewDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PutCalendarAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PutDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PutFilterAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PutJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(RevertModelSnapshotAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(SetUpgradeModeAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(StartDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(StopDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(UpdateCalendarJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(UpdateDatafeedAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(UpdateFilterAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(UpdateJobAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(UpdateModelSnapshotAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(UpdateProcessAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(ValidateDetectorAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(ValidateJobConfigAction.NAME, request, authentication), is(false)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); assertNoAccessAllowed(role, "foo"); @@ -1079,19 +1096,20 @@ public void testMachineLearningUserRole() { public void testDataFrameTransformsAdminRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("data_frame_transforms_admin"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(DeleteDataFrameTransformAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDataFrameTransformsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDataFrameTransformsStatsAction.NAME, request), is(true)); - assertThat(role.cluster().check(PreviewDataFrameTransformAction.NAME, request), is(true)); - assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request), is(true)); - assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request), is(true)); - assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request), is(true)); + assertThat(role.cluster().check(DeleteDataFrameTransformAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDataFrameTransformsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDataFrameTransformsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PreviewDataFrameTransformAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request, authentication), is(true)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); assertOnlyReadAllowed(role, ".data-frame-notifications-1"); @@ -1115,19 +1133,20 @@ public void testDataFrameTransformsAdminRole() { public void testDataFrameTransformsUserRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("data_frame_transforms_user"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(DeleteDataFrameTransformAction.NAME, request), is(false)); - assertThat(role.cluster().check(GetDataFrameTransformsAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetDataFrameTransformsStatsAction.NAME, request), is(true)); - assertThat(role.cluster().check(PreviewDataFrameTransformAction.NAME, request), is(false)); - assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request), is(false)); - assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request), is(false)); - assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request), is(false)); + assertThat(role.cluster().check(DeleteDataFrameTransformAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(GetDataFrameTransformsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetDataFrameTransformsStatsAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(PreviewDataFrameTransformAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(PutDataFrameTransformAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(StartDataFrameTransformAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(StopDataFrameTransformAction.NAME, request, authentication), is(false)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); assertOnlyReadAllowed(role, ".data-frame-notifications-1"); @@ -1151,20 +1170,21 @@ public void testDataFrameTransformsUserRole() { public void testWatcherAdminRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("watcher_admin"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(PutWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(GetWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(ExecuteWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(AckWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(ActivateWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(WatcherServiceAction.NAME, request), is(true)); - assertThat(role.cluster().check(WatcherStatsAction.NAME, request), is(true)); + assertThat(role.cluster().check(PutWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(GetWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ExecuteWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(AckWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(ActivateWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(WatcherServiceAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(WatcherStatsAction.NAME, request, authentication), is(true)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); @@ -1180,20 +1200,21 @@ public void testWatcherAdminRole() { public void testWatcherUserRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("watcher_user"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(PutWatchAction.NAME, request), is(false)); - assertThat(role.cluster().check(GetWatchAction.NAME, request), is(true)); - assertThat(role.cluster().check(DeleteWatchAction.NAME, request), is(false)); - assertThat(role.cluster().check(ExecuteWatchAction.NAME, request), is(false)); - assertThat(role.cluster().check(AckWatchAction.NAME, request), is(false)); - assertThat(role.cluster().check(ActivateWatchAction.NAME, request), is(false)); - assertThat(role.cluster().check(WatcherServiceAction.NAME, request), is(false)); - assertThat(role.cluster().check(WatcherStatsAction.NAME, request), is(true)); + assertThat(role.cluster().check(PutWatchAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(GetWatchAction.NAME, request, authentication), is(true)); + assertThat(role.cluster().check(DeleteWatchAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(ExecuteWatchAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(AckWatchAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(ActivateWatchAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(WatcherServiceAction.NAME, request, authentication), is(false)); + assertThat(role.cluster().check(WatcherStatsAction.NAME, request, authentication), is(true)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); assertThat(role.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); @@ -1252,16 +1273,17 @@ private void assertNoAccessAllowed(Role role, String index) { public void testLogstashAdminRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("logstash_admin"); assertNotNull(roleDescriptor); assertThat(roleDescriptor.getMetadata(), hasEntry("_reserved", true)); Role logstashAdminRole = Role.builder(roleDescriptor, null).build(); - assertThat(logstashAdminRole.cluster().check(ClusterHealthAction.NAME, request), is(false)); - assertThat(logstashAdminRole.cluster().check(PutIndexTemplateAction.NAME, request), is(false)); - assertThat(logstashAdminRole.cluster().check(ClusterRerouteAction.NAME, request), is(false)); - assertThat(logstashAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request), is(false)); + assertThat(logstashAdminRole.cluster().check(ClusterHealthAction.NAME, request, authentication), is(false)); + assertThat(logstashAdminRole.cluster().check(PutIndexTemplateAction.NAME, request, authentication), is(false)); + assertThat(logstashAdminRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(logstashAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); assertThat(logstashAdminRole.runAs().check(randomAlphaOfLengthBetween(1, 30)), is(false)); 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 a17cca5283211..4b0e99d7290fd 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 @@ -140,7 +140,7 @@ public void authorizeClusterAction(RequestInfo requestInfo, AuthorizationInfo au ActionListener listener) { if (authorizationInfo instanceof RBACAuthorizationInfo) { final Role role = ((RBACAuthorizationInfo) authorizationInfo).getRole(); - if (role.checkClusterAction(requestInfo.getAction(), requestInfo.getRequest())) { + if (role.checkClusterAction(requestInfo.getAction(), requestInfo.getRequest(), requestInfo.getAuthentication())) { listener.onResponse(AuthorizationResult.granted()); } else if (checkSameUserPermissions(requestInfo.getAction(), requestInfo.getRequest(), requestInfo.getAuthentication())) { listener.onResponse(AuthorizationResult.granted()); diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java index 36c9b79538272..64d6cfd938f81 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/esnative/NativeRealmIntegTests.java @@ -49,6 +49,7 @@ import org.elasticsearch.xpack.core.security.action.user.GetUsersResponse; import org.elasticsearch.xpack.core.security.action.user.PutUserRequestBuilder; import org.elasticsearch.xpack.core.security.action.user.SetEnabledRequestBuilder; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authc.support.Hasher; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.Role; @@ -369,10 +370,11 @@ public void testCreateAndUpdateRole() { } } else { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); GetRolesResponse getRolesResponse = new GetRolesRequestBuilder(client()).names("test_role").get(); assertTrue("test_role does not exist!", getRolesResponse.hasRoles()); assertTrue("any cluster permission should be authorized", - Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/foo", request)); + Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/foo", request, authentication)); preparePutRole("test_role") .cluster("none") @@ -383,7 +385,7 @@ public void testCreateAndUpdateRole() { assertTrue("test_role does not exist!", getRolesResponse.hasRoles()); assertFalse("no cluster permission should be authorized", - Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/bar", request)); + Role.builder(getRolesResponse.roles()[0], null).build().cluster().check("cluster:admin/bar", request, authentication)); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index d7f3252dd95a5..600cc6531fa4e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -119,7 +119,6 @@ import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilegeResolver; import org.elasticsearch.xpack.core.security.authz.privilege.ConfigurableClusterPrivilege; import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; -import org.elasticsearch.xpack.core.security.support.Automatons; import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.ElasticUser; import org.elasticsearch.xpack.core.security.user.KibanaUser; @@ -321,9 +320,8 @@ public void testAuthorizeUsingConditionalPrivileges() throws IOException { @Override public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) { final Predicate requestPredicate = r -> r == request; - final Predicate actionPredicate = - Automatons.predicate(((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns()); - builder.add(this, actionPredicate, requestPredicate); + builder.add(this, ((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns(), + requestPredicate); return builder; } }; @@ -348,9 +346,8 @@ public void testAuthorizationDeniedWhenConditionalPrivilegesDoNotMatch() throws @Override public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) { final Predicate requestPredicate = r -> false; - final Predicate actionPredicate = - Automatons.predicate(((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns()); - builder.add(this, actionPredicate,requestPredicate); + builder.add(this, ((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns(), + requestPredicate); return builder; } }; diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java index 4ab525a43da2f..58d19ed2c97e1 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/CompositeRolesStoreTests.java @@ -51,7 +51,6 @@ import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore; import org.elasticsearch.xpack.core.security.authz.store.RoleRetrievalResult; import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames; -import org.elasticsearch.xpack.core.security.support.Automatons; import org.elasticsearch.xpack.core.security.user.AnonymousUser; import org.elasticsearch.xpack.core.security.user.SystemUser; import org.elasticsearch.xpack.core.security.user.User; @@ -546,13 +545,13 @@ public void testMergingBasicRoles() { final TransportRequest request1 = mock(TransportRequest.class); final TransportRequest request2 = mock(TransportRequest.class); final TransportRequest request3 = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); ConfigurableClusterPrivilege ccp1 = new MockConfigurableClusterPrivilege() { @Override public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) { - Predicate predicate1 = - Automatons.predicate(((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns()); - builder.add(this, predicate1, req -> req == request1); + builder.add(this, ((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns(), + req -> req == request1); return builder; } }; @@ -582,9 +581,8 @@ public ClusterPermission.Builder buildPermission(ClusterPermission.Builder build ConfigurableClusterPrivilege ccp2 = new MockConfigurableClusterPrivilege() { @Override public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) { - Predicate predicate2 = - Automatons.predicate(((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns()); - builder.add(this, predicate2, req -> req == request2); + builder.add(this, ((ActionClusterPrivilege) ClusterPrivilegeResolver.MANAGE_SECURITY).getAllowedActionPatterns(), + req -> req == request2); return builder; } }; @@ -626,12 +624,14 @@ public ClusterPermission.Builder buildPermission(ClusterPermission.Builder build CompositeRolesStore.buildRoleFromDescriptors(Sets.newHashSet(role1, role2), cache, privilegeStore, future); Role role = future.actionGet(); - assertThat(role.cluster().check(ClusterStateAction.NAME, randomFrom(request1, request2, request3)), equalTo(true)); - assertThat(role.cluster().check(SamlAuthenticateAction.NAME, randomFrom(request1, request2, request3)), equalTo(true)); - assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, randomFrom(request1, request2, request3)), equalTo(false)); + assertThat(role.cluster().check(ClusterStateAction.NAME, randomFrom(request1, request2, request3), authentication), equalTo(true)); + assertThat(role.cluster().check(SamlAuthenticateAction.NAME, randomFrom(request1, request2, request3), authentication), + equalTo(true)); + assertThat(role.cluster().check(ClusterUpdateSettingsAction.NAME, randomFrom(request1, request2, request3), authentication), + equalTo(false)); - assertThat(role.cluster().check(PutUserAction.NAME, randomFrom(request1, request2)), equalTo(true)); - assertThat(role.cluster().check(PutUserAction.NAME, request3), equalTo(false)); + assertThat(role.cluster().check(PutUserAction.NAME, randomFrom(request1, request2), authentication), equalTo(true)); + assertThat(role.cluster().check(PutUserAction.NAME, request3, authentication), equalTo(false)); final Predicate allowedRead = role.indices().allowedIndicesMatcher(GetAction.NAME); assertThat(allowedRead.test("abc-123"), equalTo(true)); @@ -1076,7 +1076,7 @@ public void testApiKeyAuthUsesApiKeyServiceWithScopedRole() throws IOException { PlainActionFuture roleFuture = new PlainActionFuture<>(); compositeRolesStore.getRoles(authentication.getUser(), authentication, roleFuture); Role role = roleFuture.actionGet(); - assertThat(role.checkClusterAction("cluster:admin/foo", Empty.INSTANCE), is(false)); + assertThat(role.checkClusterAction("cluster:admin/foo", Empty.INSTANCE, mock(Authentication.class)), is(false)); assertThat(effectiveRoleDescriptors.get(), is(nullValue())); verify(apiKeyService).getRoleForApiKey(eq(authentication), any(ActionListener.class)); } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java index 6555dbd882377..3a2c30891008e 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/store/FileRolesStoreTests.java @@ -20,6 +20,7 @@ import org.elasticsearch.watcher.ResourceWatcherService; import org.elasticsearch.xpack.core.XPackSettings; import org.elasticsearch.xpack.core.security.audit.logfile.CapturingLogger; +import org.elasticsearch.xpack.core.security.authc.Authentication; import org.elasticsearch.xpack.core.security.authz.RoleDescriptor; import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission; import org.elasticsearch.xpack.core.security.authz.permission.IndicesPermission; @@ -351,14 +352,15 @@ public void testAutoReload() throws Exception { assertEquals(1, modifiedRoles.size()); assertTrue(modifiedRoles.contains("role5")); final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); descriptors = store.roleDescriptors(Collections.singleton("role5")); assertThat(descriptors, notNullValue()); assertEquals(1, descriptors.size()); Role role = Role.builder(descriptors.iterator().next(), null).build(); assertThat(role, notNullValue()); assertThat(role.names(), equalTo(new String[] { "role5" })); - assertThat(role.cluster().check("cluster:monitor/foo/bar", request), is(true)); - assertThat(role.cluster().check("cluster:admin/foo/bar", request), is(false)); + assertThat(role.cluster().check("cluster:monitor/foo/bar", request, authentication), is(true)); + assertThat(role.cluster().check("cluster:admin/foo/bar", request, authentication), is(false)); // truncate to remove some final Set truncatedFileRolesModified = new HashSet<>(); From ed2062f0b2772cd0df80fdc63ce00fb1830c6f0f Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad <902768+bizybot@users.noreply.github.com> Date: Fri, 23 Aug 2019 18:16:40 +1000 Subject: [PATCH 07/11] Add `manage_own_api_key` cluster privilege (#45696) This commit adds `manage_own_api_key` cluster privilege which only allows api key cluster actions on API keys owned by the currently authenticated user. Relates: #40031 --- .../security/get-builtin-privileges.asciidoc | 1 + .../authz/permission/ClusterPermission.java | 62 +++++++--- .../privilege/ClusterPrivilegeResolver.java | 5 +- .../ManageOwnApiKeyClusterPrivilege.java | 106 +++++++++++++++++ .../permission/ClusterPermissionTests.java | 2 +- .../ManageOwnApiKeyClusterPrivilegeTests.java | 110 ++++++++++++++++++ .../action/TransportGetApiKeyAction.java | 2 +- .../TransportInvalidateApiKeyAction.java | 2 +- .../xpack/security/authc/ApiKeyService.java | 18 +++ .../security/authz/AuthorizationService.java | 9 ++ .../security/authc/ApiKeyIntegTests.java | 88 ++++++++++---- .../security/authc/ApiKeyServiceTests.java | 3 + .../test/privileges/11_builtin.yml | 2 +- 13 files changed, 367 insertions(+), 43 deletions(-) create mode 100644 x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageOwnApiKeyClusterPrivilege.java create mode 100644 x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageOwnApiKeyClusterPrivilegeTests.java diff --git a/x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc b/x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc index b6afc70715a55..685f7731371ab 100644 --- a/x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-builtin-privileges.asciidoc @@ -75,6 +75,7 @@ A successful call returns an object with "cluster" and "index" fields. "manage_ingest_pipelines", "manage_ml", "manage_oidc", + "manage_own_api_key", "manage_pipeline", "manage_rollup", "manage_saml", diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java index 9d32ee9909c75..964cc1275b029 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermission.java @@ -158,32 +158,60 @@ public interface PermissionCheck { boolean implies(PermissionCheck otherPermissionCheck); } - // Automaton based permission check - private static class AutomatonPermissionCheck implements PermissionCheck { + /** + * Base for implementing cluster action based {@link PermissionCheck}. + * It enforces the checks at cluster action level and then hands it off to the implementations + * to enforce checks based on {@link TransportRequest} and/or {@link Authentication}. + */ + public abstract static class ActionBasedPermissionCheck implements PermissionCheck { private final Automaton automaton; private final Predicate actionPredicate; - AutomatonPermissionCheck(final Automaton automaton) { + public ActionBasedPermissionCheck(final Automaton automaton) { this.automaton = automaton; this.actionPredicate = Automatons.predicate(automaton); } @Override - public boolean check(final String action, final TransportRequest request, final Authentication authentication) { - return actionPredicate.test(action); + public final boolean check(final String action, final TransportRequest request, final Authentication authentication) { + return actionPredicate.test(action) && extendedCheck(action, request, authentication); } + protected abstract boolean extendedCheck(String action, TransportRequest request, Authentication authentication); + @Override - public boolean implies(final PermissionCheck permissionCheck) { - if (permissionCheck instanceof AutomatonPermissionCheck) { - return Operations.subsetOf(((AutomatonPermissionCheck) permissionCheck).automaton, this.automaton); + public final boolean implies(final PermissionCheck permissionCheck) { + if (permissionCheck instanceof ActionBasedPermissionCheck) { + return Operations.subsetOf(((ActionBasedPermissionCheck) permissionCheck).automaton, this.automaton) && + doImplies((ActionBasedPermissionCheck) permissionCheck); } return false; } + + protected abstract boolean doImplies(ActionBasedPermissionCheck permissionCheck); + } + + // Automaton based permission check + private static class AutomatonPermissionCheck extends ActionBasedPermissionCheck { + + AutomatonPermissionCheck(final Automaton automaton) { + super(automaton); + } + + @Override + protected boolean extendedCheck(String action, TransportRequest request, Authentication authentication) { + return true; + } + + @Override + protected boolean doImplies(ActionBasedPermissionCheck permissionCheck) { + return permissionCheck instanceof AutomatonPermissionCheck; + } + } // action, request based permission check - private static class ActionRequestBasedPermissionCheck extends AutomatonPermissionCheck { + private static class ActionRequestBasedPermissionCheck extends ActionBasedPermissionCheck { private final ClusterPrivilege clusterPrivilege; private final Predicate requestPredicate; @@ -195,18 +223,16 @@ private static class ActionRequestBasedPermissionCheck extends AutomatonPermissi } @Override - public boolean check(final String action, final TransportRequest request, final Authentication authentication) { - return super.check(action, request, authentication) && requestPredicate.test(request); + protected boolean extendedCheck(String action, TransportRequest request, Authentication authentication) { + return requestPredicate.test(request); } @Override - public boolean implies(final PermissionCheck permissionCheck) { - if (super.implies(permissionCheck)) { - if (permissionCheck instanceof ActionRequestBasedPermissionCheck) { - final ActionRequestBasedPermissionCheck otherCheck = - (ActionRequestBasedPermissionCheck) permissionCheck; - return this.clusterPrivilege.equals(otherCheck.clusterPrivilege); - } + protected boolean doImplies(final ActionBasedPermissionCheck permissionCheck) { + if (permissionCheck instanceof ActionRequestBasedPermissionCheck) { + final ActionRequestBasedPermissionCheck otherCheck = + (ActionRequestBasedPermissionCheck) permissionCheck; + return this.clusterPrivilege.equals(otherCheck.clusterPrivilege); } return false; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java index 755d76e76aa03..4f6e2afd9ecd1 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ClusterPrivilegeResolver.java @@ -103,6 +103,8 @@ public class ClusterPrivilegeResolver { public static final NamedClusterPrivilege MANAGE_SLM = new ActionClusterPrivilege("manage_slm", MANAGE_SLM_PATTERN); public static final NamedClusterPrivilege READ_SLM = new ActionClusterPrivilege("read_slm", READ_SLM_PATTERN); + public static final NamedClusterPrivilege MANAGE_OWN_API_KEY = ManageOwnApiKeyClusterPrivilege.INSTANCE; + private static final Map VALUES = Stream.of( NONE, ALL, @@ -131,7 +133,8 @@ public class ClusterPrivilegeResolver { MANAGE_ILM, READ_ILM, MANAGE_SLM, - READ_SLM).collect(Collectors.toUnmodifiableMap(NamedClusterPrivilege::name, Function.identity())); + READ_SLM, + MANAGE_OWN_API_KEY).collect(Collectors.toUnmodifiableMap(NamedClusterPrivilege::name, Function.identity())); /** * Resolves a {@link NamedClusterPrivilege} from a given name if it exists. diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageOwnApiKeyClusterPrivilege.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageOwnApiKeyClusterPrivilege.java new file mode 100644 index 0000000000000..bea9b16ebfc1d --- /dev/null +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageOwnApiKeyClusterPrivilege.java @@ -0,0 +1,106 @@ +/* + * + * 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.authz.privilege; + +import org.elasticsearch.common.Strings; +import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.action.CreateApiKeyRequest; +import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest; +import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission; +import org.elasticsearch.xpack.core.security.support.Automatons; + +/** + * Named cluster privilege for managing API keys owned by the current authenticated user. + */ +public class ManageOwnApiKeyClusterPrivilege implements NamedClusterPrivilege { + public static final ManageOwnApiKeyClusterPrivilege INSTANCE = new ManageOwnApiKeyClusterPrivilege(); + private static final String PRIVILEGE_NAME = "manage_own_api_key"; + private static final String API_KEY_REALM_TYPE = "_es_api_key"; + private static final String API_KEY_ID_KEY = "_security_api_key_id"; + + private ManageOwnApiKeyClusterPrivilege() { + } + + @Override + public String name() { + return PRIVILEGE_NAME; + } + + @Override + public ClusterPermission.Builder buildPermission(ClusterPermission.Builder builder) { + return builder.add(this, ManageOwnClusterPermissionCheck.INSTANCE); + } + + private static final class ManageOwnClusterPermissionCheck extends ClusterPermission.ActionBasedPermissionCheck { + public static final ManageOwnClusterPermissionCheck INSTANCE = new ManageOwnClusterPermissionCheck(); + + private ManageOwnClusterPermissionCheck() { + super(Automatons.patterns("cluster:admin/xpack/security/api_key/*")); + } + + @Override + protected boolean extendedCheck(String action, TransportRequest request, Authentication authentication) { + if (request instanceof CreateApiKeyRequest) { + return true; + } else if (request instanceof GetApiKeyRequest) { + final GetApiKeyRequest getApiKeyRequest = (GetApiKeyRequest) request; + return checkIfUserIsOwnerOfApiKeys(authentication, getApiKeyRequest.getApiKeyId(), getApiKeyRequest.getUserName(), + getApiKeyRequest.getRealmName(), getApiKeyRequest.ownedByAuthenticatedUser()); + } else if (request instanceof InvalidateApiKeyRequest) { + final InvalidateApiKeyRequest invalidateApiKeyRequest = (InvalidateApiKeyRequest) request; + return checkIfUserIsOwnerOfApiKeys(authentication, invalidateApiKeyRequest.getId(), + invalidateApiKeyRequest.getUserName(), invalidateApiKeyRequest.getRealmName(), + invalidateApiKeyRequest.ownedByAuthenticatedUser()); + } + throw new IllegalArgumentException( + "manage own api key privilege only supports API key requests (not " + request.getClass().getName() + ")"); + } + + @Override + protected boolean doImplies(ClusterPermission.ActionBasedPermissionCheck permissionCheck) { + return permissionCheck instanceof ManageOwnClusterPermissionCheck; + } + + private boolean checkIfUserIsOwnerOfApiKeys(Authentication authentication, String apiKeyId, String username, String realmName, + boolean ownedByAuthenticatedUser) { + if (isCurrentAuthenticationUsingSameApiKeyIdFromRequest(authentication, apiKeyId)) { + return true; + } else { + /* + * TODO bizybot we need to think on how we can propagate appropriate error message to the end user when username, realm name + * is missing. This is similar to the problem of propagating right error messages in case of access denied. + */ + if (authentication.getAuthenticatedBy().getType().equals(API_KEY_REALM_TYPE)) { + // API key cannot own any other API key so deny access + return false; + } else if (ownedByAuthenticatedUser) { + return true; + } else if (Strings.hasText(username) && Strings.hasText(realmName)) { + final String authenticatedUserPrincipal = authentication.getUser().principal(); + final String authenticatedUserRealm = authentication.getAuthenticatedBy().getName(); + return username.equals(authenticatedUserPrincipal) && realmName.equals(authenticatedUserRealm); + } + } + return false; + } + + private boolean isCurrentAuthenticationUsingSameApiKeyIdFromRequest(Authentication authentication, String apiKeyId) { + if (authentication.getAuthenticatedBy().getType().equals(API_KEY_REALM_TYPE)) { + // API key id from authentication must match the id from request + final String authenticatedApiKeyId = (String) authentication.getMetadata().get(API_KEY_ID_KEY); + if (Strings.hasText(apiKeyId)) { + return apiKeyId.equals(authenticatedApiKeyId); + } + } + return false; + } + } +} diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermissionTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermissionTests.java index fe08db0d2e2e0..5a52519c7b72e 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermissionTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/permission/ClusterPermissionTests.java @@ -276,7 +276,7 @@ public int hashCode() { @Override public String toString() { return "MockConfigurableClusterPrivilege{" + - "requestAuthnPredicate=" + requestPredicate + + "requestPredicate=" + requestPredicate + '}'; } diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageOwnApiKeyClusterPrivilegeTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageOwnApiKeyClusterPrivilegeTests.java new file mode 100644 index 0000000000000..c6d67b9e00b58 --- /dev/null +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/privilege/ManageOwnApiKeyClusterPrivilegeTests.java @@ -0,0 +1,110 @@ +/* + * + * 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.authz.privilege; + +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.transport.TransportRequest; +import org.elasticsearch.xpack.core.security.action.GetApiKeyRequest; +import org.elasticsearch.xpack.core.security.action.InvalidateApiKeyRequest; +import org.elasticsearch.xpack.core.security.authc.Authentication; +import org.elasticsearch.xpack.core.security.authz.permission.ClusterPermission; +import org.elasticsearch.xpack.core.security.user.User; + +import java.util.Map; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ManageOwnApiKeyClusterPrivilegeTests extends ESTestCase { + + public void testAuthenticationWithApiKeyAllowsAccessToApiKeyActionsWhenItIsOwner() { + final ClusterPermission clusterPermission = + ManageOwnApiKeyClusterPrivilege.INSTANCE.buildPermission(ClusterPermission.builder()).build(); + + final String apiKeyId = randomAlphaOfLengthBetween(4, 7); + final Authentication authentication = createMockAuthentication("joe","_es_api_key", "_es_api_key", + Map.of("_security_api_key_id", apiKeyId)); + final TransportRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(apiKeyId, randomBoolean()); + final TransportRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(apiKeyId, randomBoolean()); + + assertTrue(clusterPermission.check("cluster:admin/xpack/security/api_key/get", getApiKeyRequest, authentication)); + assertTrue(clusterPermission.check("cluster:admin/xpack/security/api_key/invalidate", invalidateApiKeyRequest, authentication)); + assertFalse(clusterPermission.check("cluster:admin/something", mock(TransportRequest.class), authentication)); + } + + public void testAuthenticationWithApiKeyDeniesAccessToApiKeyActionsWhenItIsNotOwner() { + final ClusterPermission clusterPermission = + ManageOwnApiKeyClusterPrivilege.INSTANCE.buildPermission(ClusterPermission.builder()).build(); + + final String apiKeyId = randomAlphaOfLengthBetween(4, 7); + final Authentication authentication = createMockAuthentication("joe","_es_api_key", "_es_api_key", + Map.of("_security_api_key_id", randomAlphaOfLength(7))); + final TransportRequest getApiKeyRequest = GetApiKeyRequest.usingApiKeyId(apiKeyId, randomBoolean()); + final TransportRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingApiKeyId(apiKeyId, randomBoolean()); + + assertFalse(clusterPermission.check("cluster:admin/xpack/security/api_key/get", getApiKeyRequest, authentication)); + assertFalse(clusterPermission.check("cluster:admin/xpack/security/api_key/invalidate", invalidateApiKeyRequest, authentication)); + } + + public void testAuthenticationWithUserAllowsAccessToApiKeyActionsWhenItIsOwner() { + final ClusterPermission clusterPermission = + ManageOwnApiKeyClusterPrivilege.INSTANCE.buildPermission(ClusterPermission.builder()).build(); + + final Authentication authentication = createMockAuthentication("joe","realm1", "native", Map.of()); + final TransportRequest getApiKeyRequest = GetApiKeyRequest.usingRealmAndUserName("realm1", "joe"); + final TransportRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.usingRealmAndUserName("realm1", "joe"); + + assertTrue(clusterPermission.check("cluster:admin/xpack/security/api_key/get", getApiKeyRequest, authentication)); + assertTrue(clusterPermission.check("cluster:admin/xpack/security/api_key/invalidate", invalidateApiKeyRequest, authentication)); + assertFalse(clusterPermission.check("cluster:admin/something", mock(TransportRequest.class), authentication)); + } + + public void testAuthenticationWithUserAllowsAccessToApiKeyActionsWhenItIsOwner_WithOwnerFlagOnly() { + final ClusterPermission clusterPermission = + ManageOwnApiKeyClusterPrivilege.INSTANCE.buildPermission(ClusterPermission.builder()).build(); + + final Authentication authentication = createMockAuthentication("joe","realm1", "native", Map.of()); + final TransportRequest getApiKeyRequest = GetApiKeyRequest.forOwnedApiKeys(); + final TransportRequest invalidateApiKeyRequest = InvalidateApiKeyRequest.forOwnedApiKeys(); + + assertTrue(clusterPermission.check("cluster:admin/xpack/security/api_key/get", getApiKeyRequest, authentication)); + assertTrue(clusterPermission.check("cluster:admin/xpack/security/api_key/invalidate", invalidateApiKeyRequest, authentication)); + assertFalse(clusterPermission.check("cluster:admin/something", mock(TransportRequest.class), authentication)); + } + + public void testAuthenticationWithUserDeniesAccessToApiKeyActionsWhenItIsNotOwner() { + final ClusterPermission clusterPermission = + ManageOwnApiKeyClusterPrivilege.INSTANCE.buildPermission(ClusterPermission.builder()).build(); + + final Authentication authentication = createMockAuthentication("joe", "realm1", "native", Map.of()); + final TransportRequest getApiKeyRequest = randomFrom( + GetApiKeyRequest.usingRealmAndUserName("realm1", randomAlphaOfLength(7)), + GetApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(5), "joe"), + new GetApiKeyRequest(randomAlphaOfLength(5), randomAlphaOfLength(7), null, null, false)); + final TransportRequest invalidateApiKeyRequest = randomFrom( + InvalidateApiKeyRequest.usingRealmAndUserName("realm1", randomAlphaOfLength(7)), + InvalidateApiKeyRequest.usingRealmAndUserName(randomAlphaOfLength(5), "joe"), + new InvalidateApiKeyRequest(randomAlphaOfLength(5), randomAlphaOfLength(7), null, null, false)); + + assertFalse(clusterPermission.check("cluster:admin/xpack/security/api_key/get", getApiKeyRequest, authentication)); + assertFalse(clusterPermission.check("cluster:admin/xpack/security/api_key/invalidate", invalidateApiKeyRequest, authentication)); + } + + private Authentication createMockAuthentication(String username, String realmName, String realmType, Map metadata) { + final User user = new User(username); + 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.getName()).thenReturn(realmName); + when(authenticatedBy.getType()).thenReturn(realmType); + when(authentication.getMetadata()).thenReturn(metadata); + return authentication; + } +} 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 9a3c0ed8326c9..994cb90b5f2b6 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 @@ -50,7 +50,7 @@ protected void doExecute(Task task, GetApiKeyRequest request, ActionListener PASSWORD_HASHING_ALGORITHM = new Setting<>( "xpack.security.authc.api_key.hashing.algorithm", "pbkdf2", Function.identity(), v -> { if (Hasher.getAvailableAlgoStoredHash().contains(v.toLowerCase(Locale.ROOT)) == false) { @@ -520,6 +522,7 @@ private void validateApiKeyExpiration(Map source, ApiKeyCredenti : limitedByRoleDescriptors.keySet().toArray(Strings.EMPTY_ARRAY); final User apiKeyUser = new User(principal, roleNames, null, null, metadata, true); final Map authResultMetadata = new HashMap<>(); + authResultMetadata.put(API_KEY_CREATOR_REALM, creator.get("realm")); authResultMetadata.put(API_KEY_ROLE_DESCRIPTORS_KEY, roleDescriptors); authResultMetadata.put(API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY, limitedByRoleDescriptors); authResultMetadata.put(API_KEY_ID_KEY, credentials.getId()); @@ -891,6 +894,21 @@ public void getApiKeys(String realmName, String username, String apiKeyName, Str } } + /** + * Returns realm name for the authenticated user. + * If the user is authenticated by realm type {@value API_KEY_REALM_TYPE} + * then it will return the realm name of user who created this API key. + * @param authentication {@link Authentication} + * @return realm name + */ + public static String getCreatorRealmName(final Authentication authentication) { + if (authentication.getAuthenticatedBy().getType().equals(API_KEY_REALM_TYPE)) { + return (String) authentication.getMetadata().get(API_KEY_CREATOR_REALM); + } else { + return authentication.getAuthenticatedBy().getName(); + } + } + final class CachedApiKeyHashResult { final boolean success; final char[] hash; diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java index 69153379f3b15..bd81d6db4743a 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authz/AuthorizationService.java @@ -64,6 +64,7 @@ import org.elasticsearch.xpack.security.audit.AuditLevel; import org.elasticsearch.xpack.security.audit.AuditTrailService; import org.elasticsearch.xpack.security.audit.AuditUtil; +import org.elasticsearch.xpack.security.authc.ApiKeyService; import org.elasticsearch.xpack.security.authz.interceptor.RequestInterceptor; import org.elasticsearch.xpack.security.authz.store.CompositeRolesStore; @@ -572,6 +573,14 @@ private ElasticsearchSecurityException denialException(Authentication authentica return authorizationError("action [{}] is unauthorized for user [{}] run as [{}]", cause, action, authUser.principal(), authentication.getUser().principal()); } + // check for authentication by API key + if (authentication.getAuthenticatedBy().getType().equals(ApiKeyService.API_KEY_REALM_TYPE)) { + final String apiKeyId = (String) authentication.getMetadata().get(ApiKeyService.API_KEY_ID_KEY); + assert apiKeyId != null : "api key id must be present in the metadata"; + logger.debug("action [{}] is unauthorized for API key id [{}] of user [{}]", action, apiKeyId, authUser.principal()); + return authorizationError("action [{}] is unauthorized for API key id [{}] of user [{}]", cause, action, apiKeyId, + authUser.principal()); + } logger.debug("action [{}] is unauthorized for user [{}]", action, authUser.principal()); return authorizationError("action [{}] is unauthorized for user [{}]", cause, action, authUser.principal()); } 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 bec82b17c1495..4c9e944c14f0f 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 @@ -93,7 +93,9 @@ public void wipeSecurityIndex() throws InterruptedException { public String configRoles() { return super.configRoles() + "\n" + "manage_api_key_role:\n" + - " cluster: [\"manage_api_key\"]\n"; + " cluster: [\"manage_api_key\"]\n" + + "manage_own_api_key_role:\n" + + " cluster: [\"manage_own_api_key\"]\n"; } @Override @@ -101,13 +103,15 @@ 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_manage_api_key_role:" + usersPasswdHashed + "\n" + + "user_with_manage_own_api_key_role:" + usersPasswdHashed + "\n"; } @Override public String configUsersRoles() { return super.configUsersRoles() + - "manage_api_key_role:user_with_manage_api_key_role\n"; + "manage_api_key_role:user_with_manage_api_key_role\n" + + "manage_own_api_key_role:user_with_manage_own_api_key_role\n"; } private void awaitApiKeysRemoverCompletion() throws InterruptedException { @@ -505,15 +509,16 @@ public void testGetApiKeysOwnedByCurrentAuthenticatedUser() throws InterruptedEx int noOfSuperuserApiKeys = randomIntBetween(3, 5); int noOfApiKeysForUserWithManageApiKeyRole = randomIntBetween(3, 5); List defaultUserCreatedKeys = createApiKeys(noOfSuperuserApiKeys, null); - List userWithManageApiKeyRoleApiKeys = createApiKeys("user_with_manage_api_key_role", - noOfApiKeysForUserWithManageApiKeyRole, null); + String userWithManageApiKeyRole = randomFrom("user_with_manage_api_key_role", "user_with_manage_own_api_key_role"); + List userWithManageApiKeyRoleApiKeys = createApiKeys(userWithManageApiKeyRole, + noOfApiKeysForUserWithManageApiKeyRole, null, "monitor"); final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken - .basicAuthHeaderValue("user_with_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + .basicAuthHeaderValue(userWithManageApiKeyRole, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); PlainActionFuture listener = new PlainActionFuture<>(); client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.forOwnedApiKeys(), listener); GetApiKeyResponse response = listener.get(); - verifyGetResponse("user_with_manage_api_key_role", noOfApiKeysForUserWithManageApiKeyRole, userWithManageApiKeyRoleApiKeys, + verifyGetResponse(userWithManageApiKeyRole, noOfApiKeysForUserWithManageApiKeyRole, userWithManageApiKeyRoleApiKeys, response, userWithManageApiKeyRoleApiKeys.stream().map(o -> o.getId()).collect(Collectors.toSet()), null); } @@ -521,10 +526,11 @@ public void testInvalidateApiKeysOwnedByCurrentAuthenticatedUser() throws Interr int noOfSuperuserApiKeys = randomIntBetween(3, 5); int noOfApiKeysForUserWithManageApiKeyRole = randomIntBetween(3, 5); List defaultUserCreatedKeys = createApiKeys(noOfSuperuserApiKeys, null); - List userWithManageApiKeyRoleApiKeys = createApiKeys("user_with_manage_api_key_role", - noOfApiKeysForUserWithManageApiKeyRole, null); + String userWithManageApiKeyRole = randomFrom("user_with_manage_api_key_role", "user_with_manage_own_api_key_role"); + List userWithManageApiKeyRoleApiKeys = createApiKeys(userWithManageApiKeyRole, + noOfApiKeysForUserWithManageApiKeyRole, null, "monitor"); final Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken - .basicAuthHeaderValue("user_with_manage_api_key_role", SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); + .basicAuthHeaderValue(userWithManageApiKeyRole, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); PlainActionFuture listener = new PlainActionFuture<>(); client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.forOwnedApiKeys(), listener); @@ -533,21 +539,62 @@ public void testInvalidateApiKeysOwnedByCurrentAuthenticatedUser() throws Interr verifyInvalidateResponse(noOfApiKeysForUserWithManageApiKeyRole, userWithManageApiKeyRoleApiKeys, invalidateResponse); } - public void testApiKeyAuthorizationApiKeyMustBeAbleToRetrieveItsOwnInformation() throws InterruptedException, ExecutionException { - List responses = createApiKeys(2, null); + public void testApiKeyAuthorizationApiKeyMustBeAbleToRetrieveItsOwnInformationButNotAnyOtherKeysCreatedBySameOwner() + throws InterruptedException, ExecutionException { + List responses = createApiKeys(SecuritySettingsSource.TEST_SUPERUSER,2, null, (String[]) null); final String base64ApiKeyKeyValue = Base64.getEncoder().encodeToString( (responses.get(0).getId() + ":" + responses.get(0).getKey().toString()).getBytes(StandardCharsets.UTF_8)); Client client = client().filterWithHeader(Map.of("Authorization", "ApiKey " + base64ApiKeyKeyValue)); PlainActionFuture listener = new PlainActionFuture<>(); - client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(responses.get(0).getId(), false), listener); + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(responses.get(0).getId(), randomBoolean()), listener); GetApiKeyResponse response = listener.get(); verifyGetResponse(1, responses, response, Collections.singleton(responses.get(0).getId()), null); final PlainActionFuture failureListener = new PlainActionFuture<>(); // for any other API key id, it must deny access - client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(responses.get(1).getId(), false), failureListener); + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.usingApiKeyId(responses.get(1).getId(), randomBoolean()), + failureListener); + ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, () -> failureListener.actionGet()); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", SecuritySettingsSource.TEST_SUPERUSER, + responses.get(0).getId()); + + final PlainActionFuture failureListener1 = new PlainActionFuture<>(); + client.execute(GetApiKeyAction.INSTANCE, GetApiKeyRequest.forOwnedApiKeys(), failureListener1); + ese = expectThrows(ElasticsearchSecurityException.class, () -> failureListener1.actionGet()); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", SecuritySettingsSource.TEST_SUPERUSER, + responses.get(0).getId()); + } + + public void testApiKeyWithManageOwnPrivilegeIsAbleToInvalidateItselfButNotAnyOtherKeysCreatedBySameOwner() + throws InterruptedException, ExecutionException { + List responses = createApiKeys(SecuritySettingsSource.TEST_SUPERUSER, 2, null, "manage_own_api_key"); + final String base64ApiKeyKeyValue = Base64.getEncoder().encodeToString( + (responses.get(0).getId() + ":" + responses.get(0).getKey().toString()).getBytes(StandardCharsets.UTF_8)); + Client client = client().filterWithHeader(Map.of("Authorization", "ApiKey " + base64ApiKeyKeyValue)); + + final PlainActionFuture failureListener = new PlainActionFuture<>(); + // for any other API key id, it must deny access + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(responses.get(1).getId(), randomBoolean()), + failureListener); ElasticsearchSecurityException ese = expectThrows(ElasticsearchSecurityException.class, () -> failureListener.actionGet()); - assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/get", SecuritySettingsSource.TEST_SUPERUSER); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", SecuritySettingsSource.TEST_SUPERUSER, + responses.get(0).getId()); + + final PlainActionFuture failureListener1 = new PlainActionFuture<>(); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.forOwnedApiKeys(), failureListener1); + ese = expectThrows(ElasticsearchSecurityException.class, () -> failureListener1.actionGet()); + assertErrorMessage(ese, "cluster:admin/xpack/security/api_key/invalidate", SecuritySettingsSource.TEST_SUPERUSER, + responses.get(0).getId()); + + PlainActionFuture listener = new PlainActionFuture<>(); + client.execute(InvalidateApiKeyAction.INSTANCE, InvalidateApiKeyRequest.usingApiKeyId(responses.get(0).getId(), randomBoolean()), + listener); + InvalidateApiKeyResponse invalidateResponse = listener.get(); + + assertThat(invalidateResponse.getInvalidatedApiKeys().size(), equalTo(1)); + assertThat(invalidateResponse.getInvalidatedApiKeys(), containsInAnyOrder(responses.get(0).getId())); + assertThat(invalidateResponse.getPreviouslyInvalidatedApiKeys().size(), equalTo(0)); + assertThat(invalidateResponse.getErrors().size(), equalTo(0)); } private void verifyGetResponse(int expectedNumberOfApiKeys, List responses, @@ -582,13 +629,13 @@ private void verifyGetResponse(String user, int expectedNumberOfApiKeys, List createApiKeys(int noOfApiKeys, TimeValue expiration) { - return createApiKeys(SecuritySettingsSource.TEST_SUPERUSER, noOfApiKeys, expiration); + return createApiKeys(SecuritySettingsSource.TEST_SUPERUSER, noOfApiKeys, expiration, "monitor"); } - private List createApiKeys(String user, int noOfApiKeys, TimeValue expiration) { + private List createApiKeys(String user, int noOfApiKeys, TimeValue expiration, String... clusterPrivileges) { List responses = new ArrayList<>(); for (int i = 0; i < noOfApiKeys; i++) { - final RoleDescriptor descriptor = new RoleDescriptor("role", new String[] { "monitor" }, null, null); + final RoleDescriptor descriptor = new RoleDescriptor("role", clusterPrivileges, null, null); Client client = client().filterWithHeader(Collections.singletonMap("Authorization", UsernamePasswordToken .basicAuthHeaderValue(user, SecuritySettingsSourceField.TEST_PASSWORD_SECURE_STRING))); final CreateApiKeyResponse response = new CreateApiKeyRequestBuilder(client) @@ -602,7 +649,8 @@ private List createApiKeys(String user, int noOfApiKeys, T return responses; } - private void assertErrorMessage(final ElasticsearchSecurityException ese, String action, String userName) { - assertThat(ese.getMessage(), is("action [" + action + "] is unauthorized for user [" + userName + "]")); + private void assertErrorMessage(final ElasticsearchSecurityException ese, String action, String userName, String apiKeyId) { + assertThat(ese.getMessage(), + is("action [" + action + "] is unauthorized for API key id [" + apiKeyId + "] of user [" + userName + "]")); } } diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java index 0491d20d74c8a..031f5ccec0696 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authc/ApiKeyServiceTests.java @@ -191,6 +191,7 @@ public void testValidateApiKey() throws Exception { sourceMap.put("limited_by_role_descriptors", Collections.singletonMap("limited role", Collections.singletonMap("cluster", "all"))); Map creatorMap = new HashMap<>(); creatorMap.put("principal", "test_user"); + creatorMap.put("realm", "realm1"); creatorMap.put("metadata", Collections.emptyMap()); sourceMap.put("creator", creatorMap); sourceMap.put("api_key_invalidated", false); @@ -209,6 +210,7 @@ public void testValidateApiKey() throws Exception { assertThat(result.getMetadata().get(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY), equalTo(sourceMap.get("role_descriptors"))); assertThat(result.getMetadata().get(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY), equalTo(sourceMap.get("limited_by_role_descriptors"))); + assertThat(result.getMetadata().get(ApiKeyService.API_KEY_CREATOR_REALM), is("realm1")); sourceMap.put("expiration_time", Clock.systemUTC().instant().plus(1L, ChronoUnit.HOURS).toEpochMilli()); future = new PlainActionFuture<>(); @@ -222,6 +224,7 @@ public void testValidateApiKey() throws Exception { assertThat(result.getMetadata().get(ApiKeyService.API_KEY_ROLE_DESCRIPTORS_KEY), equalTo(sourceMap.get("role_descriptors"))); assertThat(result.getMetadata().get(ApiKeyService.API_KEY_LIMITED_ROLE_DESCRIPTORS_KEY), equalTo(sourceMap.get("limited_by_role_descriptors"))); + assertThat(result.getMetadata().get(ApiKeyService.API_KEY_CREATOR_REALM), is("realm1")); sourceMap.put("expiration_time", Clock.systemUTC().instant().minus(1L, ChronoUnit.HOURS).toEpochMilli()); future = new PlainActionFuture<>(); diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/11_builtin.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/11_builtin.yml index 2e23a85b7e737..df1978f443fc1 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/11_builtin.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/11_builtin.yml @@ -15,5 +15,5 @@ setup: # This is fragile - it needs to be updated every time we add a new cluster/index privilege # I would much prefer we could just check that specific entries are in the array, but we don't have # an assertion for that - - length: { "cluster" : 28 } + - length: { "cluster" : 29 } - length: { "index" : 16 } From e6d50ffb189aaf3510f49d48402dbb90cb016fec Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad <902768+bizybot@users.noreply.github.com> Date: Fri, 23 Aug 2019 20:17:54 +1000 Subject: [PATCH 08/11] Document API key API changes for `owner` flag (#45698) This commit adds documentation for owner flag for API key APIs. By default, this flag is false and when set to true restrict the API key operations to the API keys owned by the currently authenticated user. Relates: #40031 --- .../rest-api/security/get-api-keys.asciidoc | 44 +++++++++++++++++-- .../security/invalidate-api-keys.asciidoc | 39 ++++++++++++++-- 2 files changed, 77 insertions(+), 6 deletions(-) diff --git a/x-pack/docs/en/rest-api/security/get-api-keys.asciidoc b/x-pack/docs/en/rest-api/security/get-api-keys.asciidoc index 7201a3f324d0e..1aa47ebab64cc 100644 --- a/x-pack/docs/en/rest-api/security/get-api-keys.asciidoc +++ b/x-pack/docs/en/rest-api/security/get-api-keys.asciidoc @@ -39,13 +39,20 @@ pertain to retrieving api keys: `realm_name`:: (Optional, string) The name of an authentication realm. This parameter cannot be -used with either `id` or `name`. +used with either `id` or `name` or when `owner` flag is set to `true`. `username`:: (Optional, string) The username of a user. This parameter cannot be used with -either `id` or `name`. +either `id` or `name` or when `owner` flag is set to `true`. -NOTE: While all parameters are optional, at least one of them is required. +`owner`:: +(Optional, boolean) A boolean flag that can be used to query API keys owned +by the currently authenticated user. Defaults to false. +The 'realm_name' or 'username' parameters cannot be specified when this +parameter is set to 'true' as they are assumed to be the currently authenticated ones. + +NOTE: At least one of "id", "name", "username" and "realm_name" must be specified + if "owner" is "false" (default). [[security-api-get-api-key-example]] ==== {api-examples-title} @@ -114,6 +121,37 @@ GET /_security/api_key?username=myuser // CONSOLE // TEST[continued] +The following example retrieves all API keys owned by the currently authenticated user: + +[source,js] +-------------------------------------------------- +GET /_security/api_key?owner=true +-------------------------------------------------- +// CONSOLE +// TEST[continued] + +Following creates an API key + +[source, js] +------------------------------------------------------------ +POST /_security/api_key +{ + "name": "my-api-key-1" +} +------------------------------------------------------------ +// CONSOLE + +The following example retrieves the API key identified by the specified `id` if +it is owned by the currently authenticated user: + +[source,js] +-------------------------------------------------- +GET /_security/api_key?id=VuaCfGcBCdbkQm-e5aOx&owner=true +-------------------------------------------------- +// CONSOLE +// TEST[s/VuaCfGcBCdbkQm-e5aOx/$body.id/] +// TEST[continued] + Finally, the following example retrieves all API keys for the user `myuser` in the `native1` realm immediately: diff --git a/x-pack/docs/en/rest-api/security/invalidate-api-keys.asciidoc b/x-pack/docs/en/rest-api/security/invalidate-api-keys.asciidoc index a5cdcb1821e81..ecd79a0906cfd 100644 --- a/x-pack/docs/en/rest-api/security/invalidate-api-keys.asciidoc +++ b/x-pack/docs/en/rest-api/security/invalidate-api-keys.asciidoc @@ -40,13 +40,20 @@ pertain to invalidating api keys: `realm_name`:: (Optional, string) The name of an authentication realm. This parameter cannot be -used with either `id` or `name`. +used with either `id` or `name` or when `owner` flag is set to `true`. `username`:: (Optional, string) The username of a user. This parameter cannot be used with -either `id` or `name`. +either `id` or `name` or when `owner` flag is set to `true`. -NOTE: While all parameters are optional, at least one of them is required. +`owner`:: +(Optional, boolean) A boolean flag that can be used to query API keys owned +by the currently authenticated user. Defaults to false. +The 'realm_name' or 'username' parameters cannot be specified when this +parameter is set to 'true' as they are assumed to be the currently authenticated ones. + +NOTE: At least one of "id", "name", "username" and "realm_name" must be specified + if "owner" is "false" (default). [[security-api-invalidate-api-key-response-body]] ==== {api-response-body-title} @@ -138,6 +145,32 @@ DELETE /_security/api_key // CONSOLE // TEST +The following example invalidates the API key identified by the specified `id` if + it is owned by the currently authenticated user immediately: + +[source,js] +-------------------------------------------------- +DELETE /_security/api_key +{ + "id" : "VuaCfGcBCdbkQm-e5aOx", + "owner" : "true" +} +-------------------------------------------------- +// CONSOLE + +The following example invalidates all API keys owned by the currently authenticated + user immediately: + +[source,js] +-------------------------------------------------- +DELETE /_security/api_key +{ + "owner" : "true" +} +-------------------------------------------------- +// CONSOLE +// TEST + Finally, the following example invalidates all API keys for the user `myuser` in the `native1` realm immediately: From a562c3d0d9aec81f858638bf23bc5c26e54bee3c Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad <902768+bizybot@users.noreply.github.com> Date: Tue, 27 Aug 2019 08:59:05 +1000 Subject: [PATCH 09/11] Add the code back removed due to conflict resolve --- .../core/security/authz/store/ReservedRolesStoreTests.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java index 4aac2f74da9ea..2190a0a260eeb 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java @@ -264,6 +264,9 @@ public void testIngestAdminRole() { assertThat(ingestAdminRole.cluster().check(PutPipelineAction.NAME, request, authentication), is(true)); assertThat(ingestAdminRole.cluster().check(GetPipelineAction.NAME, request, authentication), is(true)); assertThat(ingestAdminRole.cluster().check(DeletePipelineAction.NAME, request, authentication), is(true)); + assertThat(ingestAdminRole.cluster().check(ClusterRerouteAction.NAME, request, authentication), is(false)); + assertThat(ingestAdminRole.cluster().check(ClusterUpdateSettingsAction.NAME, request, authentication), is(false)); + assertThat(ingestAdminRole.cluster().check(MonitoringBulkAction.NAME, request, authentication), is(false)); assertThat(ingestAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false)); assertThat(ingestAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); From d657520a39374813838394674875daf63f7aa406 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 27 Aug 2019 13:23:11 +1000 Subject: [PATCH 10/11] resolve compilation failures due to merge --- .../security/authz/store/ReservedRolesStoreTests.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java index 2190a0a260eeb..ecedfc0c0e95e 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/store/ReservedRolesStoreTests.java @@ -328,7 +328,7 @@ public void testKibanaSystemRole() { // Everything else assertThat(kibanaRole.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); - assertThat(kibanaRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(true)); + assertThat(kibanaRole.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(true)); assertThat(kibanaRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); assertThat(kibanaRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false)); @@ -925,6 +925,7 @@ public void testAPMSystemRole() { public void testAPMUserRole() { final TransportRequest request = mock(TransportRequest.class); + final Authentication authentication = mock(Authentication.class); final RoleDescriptor roleDescriptor = new ReservedRolesStore().roleDescriptor("apm_user"); assertNotNull(roleDescriptor); @@ -932,7 +933,7 @@ public void testAPMUserRole() { Role role = Role.builder(roleDescriptor, null).build(); - assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request), is(false)); + assertThat(role.cluster().check(DelegatePkiAuthenticationAction.NAME, request, authentication), is(false)); assertThat(role.runAs().check(randomAlphaOfLengthBetween(1, 12)), is(false)); assertNoAccessAllowed(role, "foo"); @@ -1341,7 +1342,8 @@ public void testCodeAdminRole() { Role codeAdminRole = Role.builder(roleDescriptor, null).build(); - assertThat(codeAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, mock(TransportRequest.class)), is(false)); + assertThat(codeAdminRole.cluster().check(DelegatePkiAuthenticationAction.NAME, mock(TransportRequest.class), + mock(Authentication.class)), is(false)); assertThat(codeAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test("foo"), is(false)); assertThat(codeAdminRole.indices().allowedIndicesMatcher(IndexAction.NAME).test(".reporting"), is(false)); @@ -1368,7 +1370,8 @@ public void testCodeUserRole() { Role codeUserRole = Role.builder(roleDescriptor, null).build(); - assertThat(codeUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, mock(TransportRequest.class)), is(false)); + assertThat(codeUserRole.cluster().check(DelegatePkiAuthenticationAction.NAME, mock(TransportRequest.class), + mock(Authentication.class)), is(false)); assertThat(codeUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test("foo"), is(false)); assertThat(codeUserRole.indices().allowedIndicesMatcher(SearchAction.NAME).test(".reporting"), is(false)); From 6d9ee4edf631e65984268ffa0dac9b4dc5b959a4 Mon Sep 17 00:00:00 2001 From: Yogesh Gaikwad Date: Tue, 27 Aug 2019 18:12:08 +1000 Subject: [PATCH 11/11] increase built in cluster privilege count --- .../test/resources/rest-api-spec/test/privileges/11_builtin.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/11_builtin.yml b/x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/11_builtin.yml index df1978f443fc1..dd36e6e603080 100644 --- a/x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/11_builtin.yml +++ b/x-pack/plugin/src/test/resources/rest-api-spec/test/privileges/11_builtin.yml @@ -15,5 +15,5 @@ setup: # This is fragile - it needs to be updated every time we add a new cluster/index privilege # I would much prefer we could just check that specific entries are in the array, but we don't have # an assertion for that - - length: { "cluster" : 29 } + - length: { "cluster" : 30 } - length: { "index" : 16 }