-
Notifications
You must be signed in to change notification settings - Fork 24.9k
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
A new search API for API keys - core search function #75335
A new search API for API keys - core search function #75335
Conversation
Pinging @elastic/es-security (Team:Security) |
} else if (request instanceof QueryApiKeyRequest) { | ||
final QueryApiKeyRequest queryApiKeyRequest = (QueryApiKeyRequest) request; | ||
queryApiKeyRequest.setFilterForCurrentUser(); | ||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, this is the simple way of applying calling user's security context. It works but is probably not a long term solution. I am open to alternatives.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My preference would be to do this in an interceptor, but I don't think that's very easy to do.
Logically, it would be a case of checking whether the Role has unconditional access to some action, but that's not actually easy to do with an artibrary Authz Engine, and what action would you use?
We could take an approach like we do with the create_doc
index privilege and have different logical action names for the unrestricted query vs the restricted query.
That is,
public final class QueryApiKeyAction extends ActionType<QueryApiKeyResponse> {
public static final String QUERY_ALL = NAME + "/all"
}
And then have special case code somewhere like:
if( role.grantsAction(QueryApiKeyAction.QUERY_ALL ) ) {
request.setFilterForCurrentUser(false);
} else {
request.setFilterForCurrentUser(true);
}
I don't have a great suggestion, but this approach really bothers me and I don't want to set a precedent that gets reused for other actions. This is definitely not how we want to make this work in general.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume your example is about the interceptor approach. We don't have such an interceptor for cluster actions, but I assume it would work similarly to the interceptors for index requests. The interface is
void intercept(
RequestInfo requestInfo,
AuthorizationEngine authorizationEngine,
AuthorizationInfo authorizationInfo,
ActionListener<Void> listener);
Are you concerning about that, inside this method, we need to only rely on the fact that both AuthorizationEngine
and AuthorizationInfo
are interfaces and we need do it in a way that does not make RBACEngine
and our Role
special for at least in this method? It's challenging even with the "separate action name" approach. None of the existing methods fit this purpose really well. We can sorta fit it into AuthorizationEngine#authorizeClusterAction
. But it would be better if we can add a new method to AuthorizationEngine
. I think we'll need high bandwidth conversation on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I came up with a new approach inspired by your comment. The idea is to authorize the action a second time if it fails for the first time and it is the QueryApiKey action. I think this approach is promising because:
- It is simple. A few lines change in
AuthorizationService
- In theory works with arbitrary
AuthorizationEngine
andAuthorizationInfo
- Does not need a separate action name and works kinda smoothly with the
manage_own_api_key
privilege.
It has the overhead of a second authorization for the QueryApiKey action. But I think it is acceptable because it authorizing on name is pretty fast and it does not affect any other actions.
public void queryApiKeys(ApiKeyBoolQueryBuilder apiKeyBoolQueryBuilder, ActionListener<QueryApiKeyResponse> listener) { | ||
ensureEnabled(); | ||
final ActionListener<Collection<ApiKey>> wrappedListener = ActionListener.wrap(apiKeyInfos -> { | ||
if (apiKeyInfos.isEmpty()) { | ||
logger.debug("No active api keys found for query [{}]", apiKeyBoolQueryBuilder); | ||
listener.onResponse(QueryApiKeyResponse.emptyResponse()); | ||
} else { | ||
listener.onResponse(new QueryApiKeyResponse(apiKeyInfos)); | ||
} | ||
}, listener::onFailure); | ||
|
||
final SecurityIndexManager frozenSecurityIndex = securityIndex.freeze(); | ||
if (frozenSecurityIndex.indexExists() == false) { | ||
wrappedListener.onResponse(Collections.emptyList()); | ||
} else if (frozenSecurityIndex.isAvailable() == false) { | ||
wrappedListener.onFailure(frozenSecurityIndex.getUnavailableReason()); | ||
} else { | ||
findApiKeys(apiKeyBoolQueryBuilder, true, true, wrappedListener); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the time being, this uses the existing findApiKeys
method which in turns uses scroll. It will be changed to use normal search once the API supports pagination parameters like size
, from
etc.
@elasticmachine update branch |
} else if (request instanceof QueryApiKeyRequest) { | ||
final QueryApiKeyRequest queryApiKeyRequest = (QueryApiKeyRequest) request; | ||
queryApiKeyRequest.setFilterForCurrentUser(); | ||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My preference would be to do this in an interceptor, but I don't think that's very easy to do.
Logically, it would be a case of checking whether the Role has unconditional access to some action, but that's not actually easy to do with an artibrary Authz Engine, and what action would you use?
We could take an approach like we do with the create_doc
index privilege and have different logical action names for the unrestricted query vs the restricted query.
That is,
public final class QueryApiKeyAction extends ActionType<QueryApiKeyResponse> {
public static final String QUERY_ALL = NAME + "/all"
}
And then have special case code somewhere like:
if( role.grantsAction(QueryApiKeyAction.QUERY_ALL ) ) {
request.setFilterForCurrentUser(false);
} else {
request.setFilterForCurrentUser(true);
}
I don't have a great suggestion, but this approach really bothers me and I don't want to set a precedent that gets reused for other actions. This is definitely not how we want to make this work in general.
...e/src/main/java/org/elasticsearch/xpack/core/security/action/apikey/QueryApiKeyResponse.java
Outdated
Show resolved
Hide resolved
x-pack/plugin/security/qa/security-basic/src/javaRestTest/resources/roles.yml
Outdated
Show resolved
Hide resolved
.../qa/security-basic/src/javaRestTest/java/org/elasticsearch/xpack/security/QueryApiKeyIT.java
Outdated
Show resolved
Hide resolved
.../qa/security-basic/src/javaRestTest/java/org/elasticsearch/xpack/security/QueryApiKeyIT.java
Outdated
Show resolved
Hide resolved
.../qa/security-basic/src/javaRestTest/java/org/elasticsearch/xpack/security/QueryApiKeyIT.java
Outdated
Show resolved
Hide resolved
} else { | ||
final BoolQueryBuilder boolQueryBuilder = (BoolQueryBuilder) processedQuery; | ||
finalQuery.minimumShouldMatch(boolQueryBuilder.minimumShouldMatch()); | ||
finalQuery.adjustPureNegative(boolQueryBuilder.adjustPureNegative()); | ||
boolQueryBuilder.must().forEach(finalQuery::must); | ||
boolQueryBuilder.should().forEach(finalQuery::should); | ||
boolQueryBuilder.mustNot().forEach(finalQuery::mustNot); | ||
boolQueryBuilder.filter().forEach(finalQuery::filter); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this special case necessary? Isn't it simpler to always wrap it in a new bool so we know everything works the same way?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The intention is to always have the final query to be an instance of ApiKeyBoolQueryBuilder
because it has special handling to disable certain fields on the SearchExecutionContext level. So yes this is necessary because it converts a normal boolQueryBuilder to a ApiKeyBoolQueryBuilder
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But can't we just have the if
case used all the time? Why doesn't that do what we want?
QueryBuilder processedQuery = doProcess(queryBuilder);
finalQuery.must(processedQuery);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok. Sorry I see what you mean now. You are right! The must clause can simply accept any query builder including a bool query (now it's kinda obvious after you pointed it out ...) It's a better approach and I assume the end results are the same after lucene level query rewrite. Will update. Thanks!
return newQuery; | ||
} else if (qb instanceof MatchAllQueryBuilder) { | ||
return qb; | ||
} else if (qb instanceof IdsQueryBuilder) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to support ids
? Isn't that exposing that API key docs have their doc id set to the api-key-id?
Maybe we need to chat about this one. I think getting the right behaviour for the API Key Id search is important.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I went back and forth between ids
and term
. I understand the ids
approach exposes the internal knowledge of an API Key document use the API Key ID as its document ID. And this is non-ideal. But the term
approach has its own problem:
- How do we handle case-sensitivity? Is it not allowed? It is not obvious and could be trappy either way.
- It's not possible to support prefix, wildcard etc for the
_id
field, but it is again not obivious just from the Query DSL side.
(I remember I had some other reasons as well, but I cannot remember them right now.)
The bottom line is that the ID field will be an anonamly regardless of the approach. So we need to choose something that are less concerning. If "exposing the doc ID" is a big enough concern, I am happy to take the term
(terms
) approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation.
I assume if in the future the id for the API Key document wasn't exactly the same as the API Key id, (but the key id was a field inside the doc) then we could translate an ids
query into a term query.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it's my thinking as well. Translate ids
to a term
query (in future) is easy, but not so much the other way around.
.../security/src/main/java/org/elasticsearch/xpack/security/support/ApiKeyBoolQueryBuilder.java
Outdated
Show resolved
Hide resolved
.../security/src/main/java/org/elasticsearch/xpack/security/support/ApiKeyBoolQueryBuilder.java
Outdated
Show resolved
Hide resolved
Co-authored-by: Tim Vernum <[email protected]>
…tic#75308) Elasticsearch's server sometimes has to do things differently in order to handle searchable snapshots shards. In such cases it relies on index settings and hard coded values to know if a shard is a searchable snapshot one. This commit adds a new SearchableSnapshotsSettings class in server that provides methods to check if an index is a searchable snapshot index. This class also contains the names of some index settings related to searchable snapshots that are required by the server.
…c#75187) Warning related transformations missed the possibility to apply per single test only. Also a warning changed in elastic#67158 for indices.close so this PR also applies the transformation for 7.x test relates elastic#51816
This commit upgrades the existing SSPL licensed "ssl-config" library to include additional features that are supported by the X-Pack SSL library. This commit does not make any changes to X-Pack to use these new features - it introduces them in preparation for their future use by X-Pack. The reindex module is updated to reflect API changes in ssl-config
…snapshots (elastic#74977) Today there is no relationship between the lifecycle of a snapshot mounted as an index and the lifecycle of index itself. This lack of relationship makes it possible to delete a snapshot in a repository while the mounted index still exists, causing the shards to fail in the future. On the other hand creating a snapshot that contains a single index to later mount it as a searchable snapshot index becomes more and more natural for users and ILM; but it comes with the risk to forget to delete the snapshot when the searchable snapshot index is deleted from the cluster, "leaking" snapshots in repositories. We'd like to improve the situation and provide a way to automatically delete the snapshot when the mounted index is deleted. To opt in for this behaviour, a user has to enable a specific index setting when mounting the snapshot (the proposed name for the setting is index.store.snapshot.delete_searchable_snapshot). Elasticsearch then verifies that the snapshot to mount contains only 1 snapshotted index and that the snapshot is not used by another mounted index with a different value for the opt in setting, and mounts the snapshot with the new private index setting. This is the part implemented in this commit. In follow-up pull requests this index setting will be used when the last mounted index is deleted and removed from the cluster state in order to add the searchable snapshot id to a list of snapshots to delete in the repository metadata. Snapshots that are marked as "to delete" will not be able to be restored or cloned, and Elasticsearch will take care of deleting the snapshot as soon as possible. Then ILM will be changed to use this setting when mounting a snapshot as a cold or frozen index and delete_searchable_snapshot option in ILM will be removed. Finally, deleting a snapshot that is still used by mounted indices will be prevented.
This PR adds a new API for searching API keys. The API supports searching API keys with a controlled list of field names and a subset of Query DSL. It also provides a translation layer between the field names used in the REST layer and those in the index layer. This is to prevent tight coupling between the user facing request and index mappings so that they can evolve separately. Compared to the Get API key API, this new search API automatically applies calling user's security context similar to regular searches, e.g. if the user has only manage_own_api_key privilege, only keys owned by the user are returned in the search response. Relates: elastic#71023
This PR adds a new API for searching API keys. The API supports searching API keys with a controlled list of field names and a subset of Query DSL. It also provides a translation layer between the field names used in the REST layer and those in the index layer. This is to prevent tight coupling between the user facing request and index mappings so that they can evolve separately. Compared to the Get API key API, this new search API automatically applies calling user's security context similar to regular searches, e.g. if the user has only manage_own_api_key privilege, only keys owned by the user are returned in the search response. Relates: #71023
This PR adds a new API for searching API keys. The API supports searching API keys with a controlled list of field names and a subset of Query DSL. It also provides a translation layer between the field names used in the REST layer and those in the index layer. This is to prevent tight coupling between the user facing request and index mappings so that they can evolve separately. Compared to the Get API key API, this new search API automatically applies calling user's security context similar to regular searches, e.g. if the user has only manage_own_api_key privilege, only keys owned by the user are returned in the search response. Relates: elastic#71023
This PR fixed an old bug and a new bug introduced #75335. Interestingly, the two bugs somewhat cancelled each other in tests. In addition, the test setup also contributed to the overall issue. The old bug is about filtering out expired API keys, but the relationship was wrong in the search query. The new bug is that _id field should be allowed in the index level for the new API key search API. Because of the old bug, the query always gets rewritten because the tests do not have any API keys that are expired before the query time. The query rewriting effectively bypasses the _id field check. Hence the new bug is not triggered.
This PR fixed an old bug and a new bug introduced elastic#75335. Interestingly, the two bugs somewhat cancelled each other in tests. In addition, the test setup also contributed to the overall issue. The old bug is about filtering out expired API keys, but the relationship was wrong in the search query. The new bug is that _id field should be allowed in the index level for the new API key search API. Because of the old bug, the query always gets rewritten because the tests do not have any API keys that are expired before the query time. The query rewriting effectively bypasses the _id field check. Hence the new bug is not triggered.
This PR fixed an old bug and a new bug introduced #75335. Interestingly, the two bugs somewhat cancelled each other in tests. In addition, the test setup also contributed to the overall issue. The old bug is about filtering out expired API keys, but the relationship was wrong in the search query. The new bug is that _id field should be allowed in the index level for the new API key search API. Because of the old bug, the query always gets rewritten because the tests do not have any API keys that are expired before the query time. The query rewriting effectively bypasses the _id field check. Hence the new bug is not triggered.
This PR adds HLRC for the new Query API key API added with elastic#75335 and elastic#76144 Relates: elastic#71023
This PR adds documentation for the new QueryApiKey API added in elastic#75335 and elastic#76144 Relates: elastic#71023
This PR adds documentation for the new QueryApiKey API added in elastic#75335 and elastic#76144 Relates: elastic#71023
This PR adds a new API for searching API keys. The API supports searching API keys with a controlled list of field names and a subset of Query DSL. It also provides a translation layer between the field names used in the REST layer and those in the index layer. This is to prevent tight coupling between the user facing request and index mappings so that they can evolve separately.
Compared to the Get API key API, this new search API automatically applies calling user's security context similar to regular searches, e.g. if the user has only
manage_own_api_key
privilege, only keys owned by the user are returned in the search response.Relates: #71023
Supercedes: #73705