-
Notifications
You must be signed in to change notification settings - Fork 2.9k
AWS, Core, GCP: Auth Manager API enablement #12197
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
Conversation
jbonofre
left a comment
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.
LGTM, I just wonder if S3FileIO is not impacted (around the credentials).
|
Hello! Could you please clarify the behaviour in the following case: And the problem is that the token lifetime that we send via |
Hi, is If so, your The ideal scenario would be something like this:
The problem with this scenario is that token refreshes are rather broken with external IDPs, so the cached auth session will fail to keep the token refreshed. |
|
@adutra thank you for clarification! I see that technically it is possible to use not only credentials but just a token too. And when we build I'm actually curious whether it is possible to delegate token control to the outside (Trino for example, because trino has its own oauth2 management process) completely and always use for authorization a token that we send via |
Correct, and in this case, there will be no token refresh. If the token expires before the cache eviction, you would get an error.
Right now no, but with the new AuthManager API, yes. You would need to implement the |
c4a5418 to
eaedc13
Compare
|
@danielcweeks and @nastra: Can we please start the review on this? |
aws/src/test/java/org/apache/iceberg/aws/s3/signer/TestS3RestSigner.java
Outdated
Show resolved
Hide resolved
|
|
||
| @SuppressWarnings("immutables:incompat") | ||
| private static volatile Cache<String, AuthSession> authSessionCache; | ||
| private volatile RESTClient httpClient; |
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'm a little concerned that this is going to be an issue. It looks like we're making a separate AuthMangaer, AuthSession, and RESTClient for each signer instance. I feel like we could reuse at least the http client by leaving it static (that potentially consumes the most resources).
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.
Are you sure? I always found this dangerous:
iceberg/aws/src/main/java/org/apache/iceberg/aws/s3/signer/S3V4RestSignerClient.java
Lines 192 to 193 in be9808f
| HTTPClient.builder(properties()) | |
| .uri(baseSignerUri()) |
IOW the HTTPClient is static, but is built based on instance methods. If the value returned by properties() or by baseSignerUri() changes, the changes won't be reflected in a previously-created HTTPClient instance.
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, this isn't something we can consider a lightweight resource and because we bind the FileIO to a table, this could result in significant numbers of instances being created especially in cases where we distribute these references.
It isn't ideal that there is potential for different property values, but I would take that risk over the impact of having runaway client creation. The properties are mostly to configure aspects of the http implementation (I think max retries is the only referenced value).
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.
OK, I will make httpClient static again 👍
| public RESTSessionCatalog( | ||
| Function<Map<String, String>, RESTClient> clientBuilder, | ||
| BiFunction<SessionContext, Map<String, String>, FileIO> ioBuilder, | ||
| BiFunction<String, Map<String, String>, AuthManager> authManagerBuilder) { |
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 there a reason we need to expose this? I really don't love tracking a BiFunction<...> as a member reference and this is adding something that I don't think is currently exposed. I don't see anywhere this is actually used either, so it seems we could follow up if there's a use case, but for now, we can just encapsulate it.
There are also other places where this is not configurable (e.g. SigV4 and vended credentials), so we're not providing a consistent way to load this, so I would just avoid the complexity for now.
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, I introduced this for our Trino friends, see this comment for context:
TLDR, Trino would like to create catalogs with an externally-provided AuthManager. I think the use case is valid.
But OK, let's remove for now and reassess later. They can still use reflection to create their AuthManager.
\cc @varpa89
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.
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.
I believe we exposed this mainly for Trino so that Trino is able to use their own FileIO builder
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'm open to discussing how to support use cases like this, but I don't think this is the right way to go about it. Passing around functional references with little context isn't a great interface. Let's take it out for now and figure out how to do this in a cleaner and more consistent way.
|
@adutra A few comments, but I'm also running into issues with the SigV4 signer implementation while testing this. Trying to track down what the behavior difference is. |
| } else if (credentialProvided()) { | ||
| properties.put(OAuth2Properties.CREDENTIAL, credential()); | ||
| } | ||
| authSession = authManager.catalogSession(client, properties.buildKeepingLast()); |
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.
Shouldn't this be a tableSession?
I guess we don't have the parent session, so we're using contextual here. Somehow we're not getting the right auth though. I see the request is getting an Auth header, but something is different about how the auth is being applied.
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.
Shouldn't this be a tableSession?
The auth session is not static anymore, so its lifespan is now tied to the lifespan of the S3Client. Therefore, I think catalogSession makes sense here. We don't need to care about caching many auth sessions anymore (that was required before only because the old authSessionCache field was static.)
Somehow we're not getting the right auth though. I see the request is getting an Auth header, but something is different about how the auth is being applied.
I'm sorry to hear that. This class has always been the most problematic one. Do you have more details about what's wrong with the auth headers?
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.
Hmm I just spotted a subtle difference between main and this branch: on main, the credential property is always included in the auth session, even if there is also a token property:
iceberg/aws/src/main/java/org/apache/iceberg/aws/s3/signer/S3V4RestSignerClient.java
Line 223 in be9808f
| .credential(credential()) |
In this branch, the credential property is only included if there is no token:
iceberg/aws/src/main/java/org/apache/iceberg/aws/s3/signer/S3V4RestSignerClient.java
Line 156 in 75ff5d7
| } else if (credentialProvided()) { |
This may trigger a different behavior in OAuth2Manager, since the manager always tries to fetch a token if credential is present:
| } else if (config.credential() != null && !config.credential().isEmpty()) { |
I will update this branch to reflect what's in main.
Let me know if that solves your issue 🤞
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 pulled the latest and the issue persists. I'll try to hunt down why we're seeing a behavioral difference. I did a quick bisect and it works prior to (61b9dba) - Switch RESTSessionCatalog to AuthManager API, so it corresponds to the cutover to the new auth.
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.
ok, I've done a little digging and it's related to this. What's happening is that this is getting rescoped to the catalog when it needs to be scoped to the table. The loadTable returns a response with a token provided in the LoadTableResponse::config. That token gets passed to the FileIO to be used for a table-scoped auth session.
At this point however, we're doing another exchange (which we should not) using the provided table token to exchange back for a new token. At this point the session should be using the provided token and this should not be a catalog scoped session.
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 pushed yet another change that I believe achieves 100% compatibility with the old code. The change consists of preferring token over credential in OAuth2Manager.catalogSession(). This way, if the properties contain a token, that token is used, and no token fetch happens (I manually validated that.)
I still think it's best to use OAuth2Manager.catalogSession() here rather than tableSession() – even if sometimes the FileIO is table-scoped. The method is not well named imho; I offered a while ago to rename it to mainSession or genericSession. If you agree it's still time to rename it.
98d37de to
088a112
Compare
|
|
||
| @ParameterizedTest | ||
| @MethodSource("validOAuth2Properties") | ||
| void authSessionOAuth2(Map<String, String> properties, String expectedToken) throws Exception { |
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.
@danielcweeks as agreed offline, here are some tests to exercise different combinations of token and credential.
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 adding these tests
|
@nastra @danielcweeks can I get another review please? 🙏 |
| properties.put(OAuth2Properties.CREDENTIAL, credential()); | ||
| } | ||
|
|
||
| authSession = authManager.catalogSession(httpClient(), properties.buildKeepingLast()); |
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.
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.
@danielcweeks why is it "fundamentally" a table session? It can also be a catalog-scoped session, depending on whether the FileIO instance is catalog-scoped, or table-scoped.
What I think is happening is that the method name is not good.
I offered a few times to change the method name from catalogSession to mainSession or genericSession. I think that would solve the inconsistency here without requiring deep changes in the API.
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.
All Table access is via a table's FileIO. That is scoped to a table, not a catalog. This is a pretty core concept to how we think about how access flows from a table. There are a number of reasons for this which largely overlap with resource isolation and access controls. Signing requests for a specific table falls into that same category. We're creating this built based on table properties from the table load, it's aligned with table access, but we're calling it a catalog session.
I think the discussion we had around naming is still correct. There are specific session scopes for init/catalog/table that all have context and meaning.
The issue here is that it feels like we're trying to justify the implementation because it doesn't align with the signatures of the auth session.
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 point was rather around the fact that that are FileIO instances that are table scoped:
| private FileIO tableFileIO(SessionContext context, Map<String, String> config) { |
But there are FileIO instances that are catalog scoped:
| this.io = newFileIO(SessionContext.createEmpty(), mergedProps); |
It is therefore imho not correct to state that a FIleIO "is scoped to a table, not a catalog".
This component (S3V4RestSignerClient) should therefore make no assumptions about whether it is being created with catalog or table scope. It should be able to operate normally regardless of that. Also, it cannot know which table it is being created for.
That is why i think the only method that makes sense here is catalogSession. It simply creates an AuthSession from a Map of properties, which can contain configuration for a catalog, or for a table.
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 took a new look, and in the RESTSessionCatalog I see two "access layers":
- one is "at table level" with
tableFileIO - one is "at global level" with
newFileIO
So, my understanding is that we have the two "paths" in the session. So, it looks correct to use catalogSession here.
I agree that it's maybe a bit confusing and maybe worth to clarify this as a separate discussion (in a PR or dev mailing list).
I would propose to move forward on this PR, and then clarify "session path/access" on the dev mailing list.
Thoughts ?
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.
Maybe we can add a new method to clarify the use ?
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's basically what I'm suggesting. Just add:
AuthSession tableSession(RESTClient sharedClient, Map<String, String> properties);
To the AuthManager interface (it can basically use the same implementation as the catalog), but at least we're explicit about the scope under which the access is being performed. Right now, it happens to behave the way we want because it's taking the table's token from the properties.
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.
@danielcweeks yeah it makes sense to me. 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.
sorry for the late reply here. I do agree with @danielcweeks's obersation here. Fundamentally this is a table session because the token is scoped down to a table and using catalogSession just makes things conceptually confusing. Introducing & using tableSession would make much sense here and IMO should be done as part of this PR
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.
OK, method added.
This cache was mainly there because the cache, and the HTTPClient, were static fields before, and weren't closed when the signer client and its parent S3Client were closed.
e82bbd6 to
9d24964
Compare
|
Cool. We have all the approvals and build passed. Thanks everyone for the review and @adutra for working on this! |
|
I wanted to extend a huge thank you to everyone involved in making this happen! I am especially grateful to @danielcweeks and @nastra for your patience and support. |
apache#12197 accidentally changed the other of additional params inclusion in the S3 signer properties. This creates a regression when users have a custom scope, since custom scopes are included in the additional params. Such custom scopes are not being included anymore. This changes fixes this. Compare before: https://github.com/apache/iceberg/blame/57ec405a651b99d5fce3f3b4bec217d24bc98d20/aws/src/main/java/org/apache/iceberg/aws/s3/signer/S3V4RestSignerClient.java#L221-L227 With after: https://github.com/apache/iceberg/blame/071d5606bc6199a0be9b3f274ec7fbf111d88821/aws/src/main/java/org/apache/iceberg/aws/s3/signer/S3V4RestSignerClient.java#L163-L166
#12197 accidentally changed the other of additional params inclusion in the S3 signer properties. This creates a regression when users have a custom scope, since custom scopes are included in the additional params. Such custom scopes are not being included anymore.
6th and last PR for the Auth Manager API. Previous ones:
Once this PR is merged, the AuthManager API becomes effective.
Summary of changes:
OAuth2Manager, mostly to properly handle empty auth sessions, as decided in Auth Manager API part 4: RESTClient, HTTPClient #11992;HttpInterceptorcode inHTTPClientand related tests;RESTSessionCatalog;VendedCredentialsProviderandOAuth2RefreshCredentialsHandlerOAuth2RefreshCredentialsHandlernow caches itsHTTPClientandAuthSession, instead of creating a new client and session for every refresh – pretty much likeVendedCredentialsProvideralready does.