-
Notifications
You must be signed in to change notification settings - Fork 25.2k
ES|QL: Fix queries with document level security on lookup indexes #120617
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
Changes from all commits
8254efb
0427012
c40d800
edab3ec
d0b5512
1cdce58
c003b61
129133b
b3408fc
5d377a8
f16494a
151e4ed
a0bf736
23743b9
8040428
6d5b21e
17b40f8
ffa5fb9
85b33ff
72ed0b5
d070ed7
35cbc91
72328a1
0b30657
43a2d07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
pr: 120617 | ||
summary: Fix queries with document level security on lookup indexes | ||
area: ES|QL | ||
type: bug | ||
issues: [120509] |
alex-spies marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,6 +57,10 @@ public class EsqlSecurityIT extends ESRestTestCase { | |
.user("user4", "x-pack-test-password", "user4", false) | ||
.user("user5", "x-pack-test-password", "user5", false) | ||
.user("fls_user", "x-pack-test-password", "fls_user", false) | ||
.user("fls_user2", "x-pack-test-password", "fls_user2", false) | ||
.user("fls_user3", "x-pack-test-password", "fls_user3", false) | ||
.user("fls_user4_1", "x-pack-test-password", "fls_user4_1", false) | ||
.user("dls_user", "x-pack-test-password", "dls_user", false) | ||
.user("metadata1_read2", "x-pack-test-password", "metadata1_read2", false) | ||
.user("alias_user1", "x-pack-test-password", "alias_user1", false) | ||
.user("alias_user2", "x-pack-test-password", "alias_user2", false) | ||
|
@@ -92,7 +96,7 @@ private void indexDocument(String index, int id, double value, String org) throw | |
public void indexDocuments() throws IOException { | ||
Settings lookupSettings = Settings.builder().put("index.mode", "lookup").build(); | ||
String mapping = """ | ||
"properties":{"value": {"type": "double"}, "org": {"type": "keyword"}} | ||
"properties":{"value": {"type": "double"}, "org": {"type": "keyword"}, "other": {"type": "keyword"}} | ||
"""; | ||
|
||
createIndex("index", Settings.EMPTY, mapping); | ||
|
@@ -163,6 +167,32 @@ public void indexDocuments() throws IOException { | |
"""); | ||
assertOK(client().performRequest(aliasRequest)); | ||
} | ||
|
||
createMultiRoleUsers(); | ||
} | ||
|
||
private void createMultiRoleUsers() throws IOException { | ||
Request request = new Request("POST", "_security/user/dls_user2"); | ||
request.setJsonEntity(""" | ||
{ | ||
"password" : "x-pack-test-password", | ||
"roles" : [ "dls_user", "dls_user2" ], | ||
"full_name" : "Test Role", | ||
"email" : "[email protected]" | ||
} | ||
"""); | ||
assertOK(client().performRequest(request)); | ||
|
||
request = new Request("POST", "_security/user/fls_user4"); | ||
request.setJsonEntity(""" | ||
{ | ||
"password" : "x-pack-test-password", | ||
"roles" : [ "fls_user4_1", "fls_user4_2" ], | ||
"full_name" : "Test Role", | ||
"email" : "[email protected]" | ||
} | ||
"""); | ||
assertOK(client().performRequest(request)); | ||
} | ||
|
||
protected MapMatcher responseMatcher(Map<String, Object> result) { | ||
|
@@ -553,25 +583,130 @@ public void testLookupJoinIndexAllowed() throws Exception { | |
); | ||
assertThat(respMap.get("values"), equalTo(List.of(List.of(40.0, "sales")))); | ||
|
||
// Alias, should find the index and the row | ||
resp = runESQLCommand("alias_user1", "ROW x = 31.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org"); | ||
// Aliases are not allowed in LOOKUP JOIN | ||
var resp2 = expectThrows( | ||
ResponseException.class, | ||
() -> runESQLCommand("alias_user1", "ROW x = 31.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org") | ||
); | ||
|
||
assertThat(resp2.getMessage(), containsString("Aliases and index patterns are not allowed for LOOKUP JOIN [lookup-first-alias]")); | ||
assertThat(resp2.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); | ||
|
||
// Aliases are not allowed in LOOKUP JOIN, regardless of alias filters | ||
resp2 = expectThrows( | ||
ResponseException.class, | ||
() -> runESQLCommand("alias_user1", "ROW x = 123.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org") | ||
); | ||
assertThat(resp2.getMessage(), containsString("Aliases and index patterns are not allowed for LOOKUP JOIN [lookup-first-alias]")); | ||
assertThat(resp2.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
public void testLookupJoinDocLevelSecurity() throws Exception { | ||
assumeTrue( | ||
"Requires LOOKUP JOIN capability", | ||
EsqlSpecTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) | ||
); | ||
|
||
Response resp = runESQLCommand("dls_user", "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value | KEEP x, org"); | ||
assertOK(resp); | ||
Map<String, Object> respMap = entityAsMap(resp); | ||
assertThat( | ||
respMap.get("columns"), | ||
equalTo(List.of(Map.of("name", "x", "type", "double"), Map.of("name", "org", "type", "keyword"))) | ||
); | ||
|
||
assertThat(respMap.get("values"), equalTo(List.of(Arrays.asList(40.0, null)))); | ||
|
||
resp = runESQLCommand("dls_user", "ROW x = 32.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value | KEEP x, org"); | ||
assertOK(resp); | ||
respMap = entityAsMap(resp); | ||
assertThat( | ||
respMap.get("columns"), | ||
equalTo(List.of(Map.of("name", "x", "type", "double"), Map.of("name", "org", "type", "keyword"))) | ||
); | ||
assertThat(respMap.get("values"), equalTo(List.of(List.of(32.0, "marketing")))); | ||
|
||
// same, but with a user that has two dls roles that allow him more visibility | ||
|
||
resp = runESQLCommand("dls_user2", "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value | KEEP x, org"); | ||
assertOK(resp); | ||
respMap = entityAsMap(resp); | ||
assertThat( | ||
respMap.get("columns"), | ||
equalTo(List.of(Map.of("name", "x", "type", "double"), Map.of("name", "org", "type", "keyword"))) | ||
); | ||
assertThat(respMap.get("values"), equalTo(List.of(List.of(31.0, "sales")))); | ||
|
||
// Alias, for a row that's filtered out | ||
resp = runESQLCommand("alias_user1", "ROW x = 123.0 | EVAL value = x | LOOKUP JOIN lookup-first-alias ON value | KEEP x, org"); | ||
assertThat(respMap.get("values"), equalTo(List.of(Arrays.asList(40.0, "sales")))); | ||
|
||
resp = runESQLCommand("dls_user2", "ROW x = 32.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value | KEEP x, org"); | ||
assertOK(resp); | ||
respMap = entityAsMap(resp); | ||
assertThat( | ||
respMap.get("columns"), | ||
equalTo(List.of(Map.of("name", "x", "type", "double"), Map.of("name", "org", "type", "keyword"))) | ||
); | ||
assertThat(respMap.get("values"), equalTo(List.of(Arrays.asList(123.0, null)))); | ||
assertThat(respMap.get("values"), equalTo(List.of(List.of(32.0, "marketing")))); | ||
|
||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
public void testLookupJoinFieldLevelSecurity() throws Exception { | ||
assumeTrue( | ||
"Requires LOOKUP JOIN capability", | ||
EsqlSpecTestCase.hasCapabilities(adminClient(), List.of(EsqlCapabilities.Cap.JOIN_LOOKUP_V12.capabilityName())) | ||
); | ||
|
||
Response resp = runESQLCommand("fls_user2", "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value"); | ||
assertOK(resp); | ||
Map<String, Object> respMap = entityAsMap(resp); | ||
assertThat( | ||
respMap.get("columns"), | ||
equalTo( | ||
List.of( | ||
Map.of("name", "x", "type", "double"), | ||
Map.of("name", "value", "type", "double"), | ||
Map.of("name", "org", "type", "keyword") | ||
) | ||
) | ||
); | ||
|
||
resp = runESQLCommand("fls_user3", "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value"); | ||
assertOK(resp); | ||
respMap = entityAsMap(resp); | ||
assertThat( | ||
respMap.get("columns"), | ||
equalTo( | ||
List.of( | ||
Map.of("name", "x", "type", "double"), | ||
Map.of("name", "value", "type", "double"), | ||
Map.of("name", "org", "type", "keyword"), | ||
Map.of("name", "other", "type", "keyword") | ||
) | ||
) | ||
|
||
); | ||
|
||
resp = runESQLCommand("fls_user4", "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value"); | ||
assertOK(resp); | ||
respMap = entityAsMap(resp); | ||
assertThat( | ||
respMap.get("columns"), | ||
equalTo( | ||
List.of( | ||
Map.of("name", "x", "type", "double"), | ||
Map.of("name", "value", "type", "double"), | ||
Map.of("name", "org", "type", "keyword") | ||
) | ||
) | ||
); | ||
|
||
ResponseException error = expectThrows( | ||
ResponseException.class, | ||
() -> runESQLCommand("fls_user4_1", "ROW x = 40.0 | EVAL value = x | LOOKUP JOIN lookup-user2 ON value") | ||
); | ||
assertThat(error.getMessage(), containsString("Unknown column [value] in right side of join")); | ||
assertThat(error.getResponse().getStatusLine().getStatusCode(), equalTo(HttpStatus.SC_BAD_REQUEST)); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, one last test suggestion - we can also add this in a separate PR if you're already about to make this ready to merge. I just noticed: all of the fls test cases have permission to read the I think it'd be interesting to test if forbidding this field for a user makes it so all look ups are null. And, if we wanna be extra paranoid, consider the case where the lookup index has duplicates for a given join key, so the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks Alex, I still have a couple of things to push, I'll add this one as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new tests confirm the above: if a role cannot see the join key in the lookup index, the query will fail with "Unknown column" |
||
|
||
public void testLookupJoinIndexForbidden() throws Exception { | ||
|
alex-spies marked this conversation as resolved.
Show resolved
Hide resolved
|
Uh oh!
There was an error while loading. Please reload this page.