Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify API key service API #44935

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,15 +31,7 @@ public TransportGetApiKeyAction(TransportService transportService, ActionFilters

@Override
protected void doExecute(Task task, GetApiKeyRequest request, ActionListener<GetApiKeyResponse> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,13 +31,7 @@ public TransportInvalidateApiKeyAction(TransportService transportService, Action

@Override
protected void doExecute(Task task, InvalidateApiKeyRequest request, ActionListener<InvalidateApiKeyResponse> 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);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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";

Expand Down Expand Up @@ -639,98 +640,41 @@ 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<InvalidateApiKeyResponse> invalidateListener) {
public void invalidateApiKeys(String realmName, String username, String apiKeyName, String apiKeyId,
ActionListener<InvalidateApiKeyResponse> 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) {
bizybot marked this conversation as resolved.
Show resolved Hide resolved
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));
}
}

private void invalidateAllApiKeys(Collection<String> apiKeyIds, ActionListener<InvalidateApiKeyResponse> invalidateListener) {
indexInvalidation(apiKeyIds, invalidateListener, null);
}

/**
* Invalidate API key for given API key id
* @param apiKeyId API key id
* @param invalidateListener listener for {@link InvalidateApiKeyResponse}
*/
public void invalidateApiKeyForApiKeyId(String apiKeyId, ActionListener<InvalidateApiKeyResponse> 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<InvalidateApiKeyResponse> 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<Collection<ApiKey>> 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<Collection<ApiKey>> listener) {
if (filterOutInvalidatedKeys) {
Expand Down Expand Up @@ -767,35 +711,28 @@ private void findApiKeys(final BoolQueryBuilder boolQuery, boolean filterOutInva
}
}

private void findApiKeyForApiKeyName(String apiKeyName, boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys,
ActionListener<Collection<ApiKey>> listener) {
private void findApiKeysForUserRealmApiKeyIdAndNameCombination(String realmName, String userName, String apiKeyName, String apiKeyId,
boolean filterOutInvalidatedKeys, boolean filterOutExpiredKeys,
ActionListener<Collection<ApiKey>> 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<Collection<ApiKey>> 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);
}
Expand All @@ -818,9 +755,9 @@ private void indexInvalidation(Collection<String> apiKeyIds, ActionListener<Inva
BulkRequestBuilder bulkRequestBuilder = client.prepareBulk();
for (String apiKeyId : apiKeyIds) {
UpdateRequest request = client
.prepareUpdate(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, apiKeyId)
.setDoc(Collections.singletonMap("api_key_invalidated", true))
.request();
.prepareUpdate(SECURITY_MAIN_ALIAS, SINGLE_MAPPING_NAME, apiKeyId)
.setDoc(Collections.singletonMap("api_key_invalidated", true))
.request();
bulkRequestBuilder.add(request);
}
bulkRequestBuilder.setRefreshPolicy(RefreshPolicy.WAIT_UNTIL);
Expand Down Expand Up @@ -924,64 +861,26 @@ private void maybeStartApiKeyRemover() {
}

/**
* Get API keys for given realm and user name.
* Get API key information for given realm, user, API key name and id combination
* @param realmName realm name
* @param userName user name
* @param listener listener for {@link GetApiKeyResponse}
*/
public void getApiKeysForRealmAndUser(String realmName, String userName, ActionListener<GetApiKeyResponse> 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<GetApiKeyResponse> 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<GetApiKeyResponse> listener) {
public void getApiKeys(String realmName, String username, String apiKeyName, String apiKeyId,
ActionListener<GetApiKeyResponse> 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));
Expand Down