Skip to content

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

Merged
merged 25 commits into from
Jan 24, 2025

Conversation

luigidellaquila
Copy link
Contributor

@luigidellaquila luigidellaquila commented Jan 22, 2025

Fixes: #120509

Make ES|QL respect document level permissions on lookup indexes.

This fix exposes a problem with aliases as lookup indexes: if a user has permissions on an alias, but not on the underlying index, the query fails with an authorization error. This needs to be investigated further and fixed. For now we'll just disable support for aliases in LOOKUP JOIN command

@luigidellaquila luigidellaquila added v8.18.0 auto-backport Automatically create backport pull requests when merged labels Jan 22, 2025
@elasticsearchmachine
Copy link
Collaborator

Hi @luigidellaquila, I've created a changelog YAML for you.

@luigidellaquila luigidellaquila marked this pull request as ready for review January 22, 2025 13:45
@elasticsearchmachine elasticsearchmachine added the Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) label Jan 22, 2025
@elasticsearchmachine
Copy link
Collaborator

Pinging @elastic/es-analytical-engine (Team:Analytics)

@luigidellaquila
Copy link
Contributor Author

luigidellaquila commented Jan 22, 2025

Failures due to #120637
Wait for it to be merged both in main and 8.x and then change the bwc mute

@@ -109,6 +112,16 @@ Collection<Failure> verify(LogicalPlan plan, BitSet partialMetrics) {
return failures.failures();
}

private void checkLookupJoin(LogicalPlan p, Failures failures) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive by comment: LookupJoin could implement PostAnalysisVerificationAware and perform the check there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @bpintea, good point, doing it now

elasticsearchmachine pushed a commit that referenced this pull request Jan 22, 2025
Muting a yaml bwc test called "alias" apparently is not possible:

```
task.skipTest("esql/190_lookup_join/alias", "LOOKUP JOIN does not support index aliases for now")
```

```
* What went wrong:
Execution failed for task ':x-pack:plugin:yamlRestCompatTestTransform'.
> class com.fasterxml.jackson.databind.node.TextNode cannot be cast to class com.fasterxml.jackson.databind.node.ArrayNode (com.fasterxml.jackson.databind.node.TextNode and com.fasterxml.jackson.databind.node.ArrayNode are in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader$InstrumentingVisitableURLClassLoader @725f7fdd)
```

I need to mute this test in
#120617
luigidellaquila added a commit to luigidellaquila/elasticsearch that referenced this pull request Jan 22, 2025
Muting a yaml bwc test called "alias" apparently is not possible:

```
task.skipTest("esql/190_lookup_join/alias", "LOOKUP JOIN does not support index aliases for now")
```

```
* What went wrong:
Execution failed for task ':x-pack:plugin:yamlRestCompatTestTransform'.
> class com.fasterxml.jackson.databind.node.TextNode cannot be cast to class com.fasterxml.jackson.databind.node.ArrayNode (com.fasterxml.jackson.databind.node.TextNode and com.fasterxml.jackson.databind.node.ArrayNode are in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader$InstrumentingVisitableURLClassLoader @725f7fdd)
```

I need to mute this test in
elastic#120617
Copy link
Member

@dnhatn dnhatn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LookupService looks good to me. Thanks @luigidellaquila

elasticsearchmachine pushed a commit that referenced this pull request Jan 22, 2025
Muting a yaml bwc test called "alias" apparently is not possible:

```
task.skipTest("esql/190_lookup_join/alias", "LOOKUP JOIN does not support index aliases for now")
```

```
* What went wrong:
Execution failed for task ':x-pack:plugin:yamlRestCompatTestTransform'.
> class com.fasterxml.jackson.databind.node.TextNode cannot be cast to class com.fasterxml.jackson.databind.node.ArrayNode (com.fasterxml.jackson.databind.node.TextNode and com.fasterxml.jackson.databind.node.ArrayNode are in unnamed module of loader org.gradle.internal.classloader.VisitableURLClassLoader$InstrumentingVisitableURLClassLoader @725f7fdd)
```

I need to mute this test in
#120617
Copy link
Contributor

@alex-spies alex-spies left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had a first look, will continue tomorrow. Looking good so far, thanks @luigidellaquila !

public void postAnalysisVerification(Failures failures) {
super.postAnalysisVerification(failures);
if (right() instanceof EsRelation esr) {
if (esr.concreteIndices().contains(esr.indexPattern()) == false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: Could there be weird edge cases where the name of the index contains e.g. an escaped wildcard character, so that an index pattern is being used, but coincidentally, it also matches a concrete index? There's no escaping in index names, right? Per the docs it doesn't seem so: https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-create-index.html

I understand this is as edge case-y as it gets; but since avoiding aliases is required to ensure permissions are respected correctly, we better try to think about every possible scenario.

Copy link
Contributor

@alex-spies alex-spies left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the iterations @luigidellaquila !

I have a final test suggestion, but otherwise LGTM.

Comment on lines 638 to 648
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(List.of(32.0, "marketing"))));

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, these are very helpful. Thanks for adding!

failures.add(
fail(
esr,
"invalid [{}] resolution in lookup mode to an index in [{}] indices",
Copy link
Contributor

@idegtiarenko idegtiarenko Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"invalid [{}] resolution in lookup mode to an index in [{}] indices",
"invalid [{}] resolution in lookup mode to [{}] indices",

Previously it was a slightly different message, not sure if it was changed intentionally.

Copy link
Contributor

@alex-spies alex-spies left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for the iterations @luigidellaquila ! I'm feeling much more confident about the security of LOOKUP JOIN now :)

TransportRequest transportRequest
) {
ThreadContext threadContext = transportService.getThreadPool().getThreadContext();
try (ThreadContext.StoredContext unused = threadContext.stashWithOrigin(ClientHelper.ENRICH_ORIGIN)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, with @dnhatn 's suggestion the whole situation is more localized and more clear.

Personally, I'd still leave a comment in place because for people unfamiliar with this code, it can be unexpected that the call to ThreadContext.stashWithOrigin will abandon the current user's authorization. (It's explained in stashWithOrigin's javadoc, but that requires an extra hop.)

Map.of("name", "org", "type", "keyword")
)
)
);
}
Copy link
Contributor

@alex-spies alex-spies Jan 24, 2025

Choose a reason for hiding this comment

The 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 value field, which is the join key.

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 LOOKUP JOIN would lead to extra rows - but without permissions granted on the join key, no extra rows should be emitted. (Otherwise, users could exfiltrate statistics about fields they have no permissions on).

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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.
I think the whole query will fail because the user doesn't see the join key in the schema, but I'll double-check

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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"

@luigidellaquila
Copy link
Contributor Author

Thanks for the reviews folks!
I addressed all the comments, if there are no further observations I'll merge it as soon as the CI is happy.

@luigidellaquila luigidellaquila enabled auto-merge (squash) January 24, 2025 10:31
@luigidellaquila luigidellaquila enabled auto-merge (squash) January 24, 2025 10:55
@luigidellaquila luigidellaquila merged commit dc195f4 into elastic:main Jan 24, 2025
15 of 16 checks passed
@elasticsearchmachine
Copy link
Collaborator

💔 Backport failed

Status Branch Result
8.x Commit could not be cherrypicked due to conflicts

You can use sqren/backport to manually backport by running backport --upstream elastic/elasticsearch --pr 120617

luigidellaquila added a commit to luigidellaquila/elasticsearch that referenced this pull request Jan 24, 2025
craigtaverner added a commit to craigtaverner/elasticsearch that referenced this pull request May 8, 2025
There are some analyser tests for this, but I thought it useful to also have a yaml test for more coverage, since there was temporarily an issue elastic#120189 with this, later fixed in elastic#120617.
craigtaverner added a commit that referenced this pull request May 9, 2025
There are some analyser tests for this, but I thought it useful to also have a yaml test for more coverage, since there was temporarily an issue #120189 with this, later fixed in #120617.
craigtaverner added a commit to craigtaverner/elasticsearch that referenced this pull request May 11, 2025
There are some analyser tests for this, but I thought it useful to also have a yaml test for more coverage, since there was temporarily an issue elastic#120189 with this, later fixed in elastic#120617.
elasticsearchmachine pushed a commit that referenced this pull request May 11, 2025
… (#128015)

* Additional yaml test for missing join key on right side (#127906)

There are some analyser tests for this, but I thought it useful to also have a yaml test for more coverage, since there was temporarily an issue #120189 with this, later fixed in #120617.

* Fix mistake in merge
jfreden pushed a commit to jfreden/elasticsearch that referenced this pull request May 12, 2025
There are some analyser tests for this, but I thought it useful to also have a yaml test for more coverage, since there was temporarily an issue elastic#120189 with this, later fixed in elastic#120617.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
:Analytics/ES|QL AKA ESQL auto-backport Automatically create backport pull requests when merged backport pending >bug Team:Analytics Meta label for analytical engine team (ESQL/Aggs/Geo) v8.18.0 v9.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ESQL: Make LOOKUP JOIN respect document level permissions
6 participants