Skip to content
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
5 changes: 5 additions & 0 deletions docs/changelog/91170.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 91170
summary: Ensure `TermsEnum` action works correctly with API keys
area: Authorization
type: bug
issues: []
Original file line number Diff line number Diff line change
Expand Up @@ -429,28 +429,44 @@ private boolean canAccess(
Collections.emptyMap()
);

// Current user has potentially many roles and therefore potentially many queries
// defining sets of docs accessible
Set<BytesReference> queries = indexAccessControl.getDocumentPermissions().getQueries();
for (BytesReference querySource : queries) {
QueryBuilder queryBuilder = DLSRoleQueryValidator.evaluateAndVerifyRoleQuery(
querySource,
scriptService,
queryShardContext.getParserConfig().registry(),
securityContext.getUser()
// Unfiltered access is allowed when both base role and limiting role allow it.
return hasMatchAllEquivalent(indexAccessControl.getDocumentPermissions().getQueries(), securityContext, queryShardContext)
&& hasMatchAllEquivalent(
indexAccessControl.getDocumentPermissions().getLimitedByQueries(),
securityContext,
queryShardContext
);
QueryBuilder rewrittenQueryBuilder = Rewriteable.rewrite(queryBuilder, queryShardContext);
if (rewrittenQueryBuilder instanceof MatchAllQueryBuilder) {
// One of the roles assigned has "all" permissions - allow unfettered access to termsDict
return true;
}
}
return false;
}
}
return true;
}

private boolean hasMatchAllEquivalent(
Set<BytesReference> queries,
SecurityContext securityContext,
SearchExecutionContext queryShardContext
) throws IOException {
if (queries == null) {
return true;
}
// Current user has potentially many roles and therefore potentially many queries
// defining sets of docs accessible
for (BytesReference querySource : queries) {
QueryBuilder queryBuilder = DLSRoleQueryValidator.evaluateAndVerifyRoleQuery(
querySource,
scriptService,
queryShardContext.getParserConfig().registry(),
securityContext.getUser()
);
QueryBuilder rewrittenQueryBuilder = Rewriteable.rewrite(queryBuilder, queryShardContext);
if (rewrittenQueryBuilder instanceof MatchAllQueryBuilder) {
// One of the roles assigned has "all" permissions - allow unfettered access to termsDict
return true;
}
}
return false;
}

private boolean canMatchShard(ShardId shardId, NodeTermsEnumRequest req) throws IOException {
if (req.indexFilter() == null || req.indexFilter() instanceof MatchAllQueryBuilder) {
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ setup:
name: "dls_all_role"
body: >
{
"cluster": [ "manage_own_api_key" ],
"indices": [
{ "names": ["test_security"], "privileges": ["read"], "query": "{\"term\": {\"ck\": \"const\"}}" }
]
Expand Down Expand Up @@ -82,6 +83,7 @@ setup:
name: "dls_some_role"
body: >
{
"cluster": [ "manage_own_api_key" ],
"indices": [
{ "names": ["test_security"], "privileges": ["read"], "query": "{\"term\": {\"foo\": \"bar_dls\"}}" }
]
Expand Down Expand Up @@ -560,4 +562,90 @@ teardown:
body: {"field": "foo", "string":"b"}
- length: {terms: 0}

---
"Test security with API keys":

- do:
headers: { Authorization: "Basic ZGxzX2FsbF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # dls_all_user
security.create_api_key:
body: >
{
"name": "dls_all_user_good_key",
"role_descriptors": {
"role-a": {
"index": [
{
"names": ["test_security"],
"privileges": ["read"],
"query": "{\"term\": {\"ck\": \"const\"}}"
}
]
}
}
}
- match: { name: "dls_all_user_good_key" }
- set: { encoded: login_creds}
- do:
headers:
Authorization: ApiKey ${login_creds} # dls_all_user good API key sees all documents
terms_enum:
index: test_security
body: { "field": "foo", "string": "b" }
- length: { terms: 1 }

- do:
headers: { Authorization: "Basic ZGxzX2FsbF91c2VyOngtcGFjay10ZXN0LXBhc3N3b3Jk" } # dls_all_user
security.create_api_key:
body: >
{
"name": "dls_all_user_bad_key",
"role_descriptors": {
"role-a": {
"index": [
{
"names": ["test_security"],
"privileges": ["read"],
"query": "{\"term\": {\"foo\": \"bar_dls\"}}"
}
]
}
}
}
- match: { name: "dls_all_user_bad_key" }
- set: { encoded: login_creds}
- do:
headers:
Authorization: ApiKey ${login_creds} # dls_all_user bad API key sees selected docs
terms_enum:
index: test_security
body: { "field": "foo", "string": "b" }
- length: { terms: 0 }

- do:
headers: { Authorization: "Basic ZGxzX3NvbWVfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" } # dls_some_user
# Create an API key with all DLS permissions, but it is still bounded by the owner user's permissions
security.create_api_key:
body: >
{
"name": "dls_some_user_key",
"role_descriptors": {
"role-a": {
"index": [
{
"names": ["test_security"],
"privileges": ["read"],
"query": "{\"term\": {\"ck\": \"const\"}}"
}
]
}
}
}
- match: { name: "dls_some_user_key" }
- set: { encoded: login_creds}
- do:
headers:
Authorization: ApiKey ${login_creds} # dls_some_user's API key sees selected user regardless of the key's role descriptor
terms_enum:
index: test_security
body: { "field": "foo", "string": "b" }
- length: { terms: 0 }