-
Notifications
You must be signed in to change notification settings - Fork 3.1k
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
Allow ViewExpression use session user explicitly #16436
Allow ViewExpression use session user explicitly #16436
Conversation
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.
It needs some additional test cases. But yeah, I like it (I worked in this area before, and it fixes one significant pain point).
core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java
Outdated
Show resolved
Hide resolved
core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java
Outdated
Show resolved
Hide resolved
Also, and I will repeat it again, ideally the |
This is also an SPI break. Just wanted to note this :) |
6bb2403
to
20b1128
Compare
Addressed. |
assertThatThrownBy(() -> assertions.query(user(USER), "SELECT * FROM nation WHERE name = 'FRANCE'")) | ||
.hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test-catalog.tiny.nation"); |
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.
Hm. I requested a test for this, but now I realize that it's actually difficult to test. The error message here (I think?) comes from the regular check if the SELECT has access to the columns, not from the check if the expression in the filter has access to them. A more proper test would require that the Identity
of the current session has some additional attributes (like roles or groups) that grant it access, but an Identity
with just the user does not. In such a case, previously the check for SELECT would be done for the former, which would pass, and the check for the mask expression would be done using the latter, which would fail. After this change the former would be reused for the latter, so both would pass.
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.
Added tests to TestAccessControl
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!
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 don't think we need this. commit was removed
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.
"Remove obselete backward compability SPI exceptions"
core/trino-spi/pom.xml
Outdated
@@ -254,28 +254,6 @@ | |||
<new>method void io.airlift.slice.SliceOutput::write(byte[])</new> | |||
<exception>java.io.IOException</exception> | |||
</item> | |||
<item> |
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.
above (<old>method void io.airlift.slice.SliceOutput::write(byte[]) throws java.io.IOException</old>
) can also be removed
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.
That is not maven said. I removed that and then I had to restore it.
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.
"Convert classes to records" LGTM
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.
"Simplify assertions" LGTM
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.
"Improve method names"
@@ -160,7 +160,7 @@ public void testRowFilterOnNotAccessibleColumn() | |||
} | |||
|
|||
@Test | |||
public void testRowFilterOnNotAccessibleColumnKO() | |||
public void testRowFilterWithUserOnNotAccessibleColumn() |
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.
What does "with user" mean? Did you mean "unprivileged user"?
"NotAccessible" -> "inaccessible"
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.
unprivileged user. I changed them again. Hopefully now is better.
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.
"Introduce @internal
SPI annotation"
public String getIdentity() | ||
{ | ||
return identity; | ||
} | ||
|
||
@Internal |
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 generally like the idea of being able to say which "direction" does given class work.
How would we enforce our connectors do not call internal APIs?
How should external connectors' maintainers to that?
In this particular case, what if one connector needs, for some reason, to rewrap a ViewExpression returned by some other connector. Can it do that? Is it illegal?
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.
https://github.com/starburstdata/error-prone-checks can be a rescue.
Anyway I am removing this commit as it not the point of this PR and it requires its own discussion.
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.
"Allow ViewExpression use session user explicitly" LGTM
would it be possible to make the change simpler?
For example, in StatamentAnalyzer have a logic conditional on session user equals view Expression's identity
?
@Internal | ||
public String getIdentity() | ||
public Optional<String> getIdentity() |
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.
Sometimes we have to introduce breaking changes.
So far we haven't been ultra strict about that, so I think it's fine to just change this method and add exclusion to revapi check. Let's defer intro of @Internal
until we have more clairty what we want to achieve.
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.
Sometimes we have to introduce breaking changes.
I added a new method instead. I think we can avoid breaking change here. It is not a big deal.
accessControl.deny(privilege(USER, "nation.comment", SELECT_COLUMN)); | ||
assertThatThrownBy(() -> assertions.query("SELECT * FROM nation WHERE name = 'FRANCE'")) | ||
.hasMessage("Access Denied: Cannot select from columns [nationkey, regionkey, name, comment] in table or view test-catalog.tiny.nation"); | ||
} | ||
|
||
@Test | ||
public void testRowFilterAsInvokerOnNotAccessibleColumn() |
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.
NotAccessible -> Inaccessible
"RowFilterAsInvoker" -- what does this mean?
do we have RUN AS INVOKER and RUN AS DEFINER row filers?
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 have RUN AS INVOKER and RUN AS DEFINER row filers?
Not at the moment, but we're trying to introduce that, basically. The general idea is that column masks and row filters are view-like constructs, which should behave similarly to views. Views have the SECURITY
clause, and with filters and masks the equivalent is the ViewExpression::identity
. Currently access controls are forced to always provide this identity, which is as if all views were SECURITY DEFINER
and the owner was the current session's user. Making this Optional
is effectively adding SECURITY INVOKER
to filters and masks. Though it could probably be named differently.
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.
"RowFilterAsInvoker" -- what does this mean?
I changed this to RunFilterAsSessionUser
.
NotAccessible -> Inaccessible
The entire file is using NotAccessible. I will change that.
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.
Not at the moment, but we're trying to introduce that, basically.
Can we make the PR title convey that?
At first this looked like an optimization -- if filter owner is dumbly filled with session user, skip re-getting groups.
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.
Can we make the PR title convey that?
I was under impression it already does convey that. See Allow ViewExpression use session user explicitly
where using session user is basically what SECURITY INVOKER is for views.
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.
You're right. It seems some things are obvious to you while they are not so obvious to me.
core/trino-main/src/test/java/io/trino/sql/query/TestFilterInaccessibleColumns.java
Outdated
Show resolved
Hide resolved
{ | ||
return Optional.ofNullable(columnConstraints.get(column)).flatMap(constraint -> | ||
constraint.getMask().map(mask -> new ViewExpression( | ||
constraint.getMaskEnvironment().flatMap(ExpressionEnvironment::getUser).orElse(user), |
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.
nice!
why tell the engine what the user is :)
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.
Yeah... engine knows it better.
.map(filterUser -> Identity.forUser(filterUser) | ||
.withGroups(groupProvider.getGroups(filterUser)) | ||
.build()) | ||
.orElseGet(session::getIdentity); |
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.
Can there be any difference between cases
- when ViewExpression has
empty
identity - when ViewExpression has identity equal to current session user?
is it worth having a code comment why we don't check equality between them?
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.
FWIW I tried to make it so that when the expression's identity is the same as the session's user then the latter is re-used, and it was vetoed. But I think making it explicit that it should be reused is a better way to go.
Also, extending the view analogy, with views we do have special handling if the current session's user is the same as the view's owner (some restrictions are relaxed then). So there may be a difference between these two, but I don't think there would be any meaningful difference in semantics.
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 tried to make it so that when the expression's identity is the same as the session's user then the latter is re-used, and it was vetoed. But I think making it explicit that it should be reused is a better way to go.
We used have that for views and it was also causing issues. When session had limited set of roles, so they couldn't select the view because view wanted more roles. It got removed. So now in views the there is no custom logic for when view owner is the same as same user. "Impersonation" works the same way for any user now for views.
This commit is going to make something similar for filters and masks.
Can there be any difference between cases
- when ViewExpression has empty identity
- when ViewExpression has identity equal to current session user?
Yes. The former will impersonate the user to itself, and so the information about roles could change.
20b1128
to
710e393
Compare
@dain Can you please elaborate? Please notice |
Before the change if access control is not returning any dedicated user to evaluate the view expression they just pass the session user. However for the engine it is not clear that is a session user and so engine needs to retrieve groups for that user again and possibly some session roles are lost. After this change access control may decide to return empty identity. That would be in line of view SECURITY INVOKER. Then engine can simply reuse the session identity.
710e393
to
16c3e44
Compare
I think we have been in this place with views where we had such condition. It has its own issues so that condition was removed from views. I don't want to introduce it for masks&filters. |
Does this need release notes? |
No. Thank you for asking. |
Allow ViewExpression use session user explicitly
Before the change if access control is not returning any dedicated user to evaluate
the view expression they just pass the session user. However for the
engine it is not clear that is a session user and so engine needs to
retrieve groups for that user again and possibly some session roles are
lost.
After this change access control may decide to return empty identity.
That would be in line of view SECURITY INVOKER. Then engine can simply
reuse the session identity.