-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
Auth Manager API part 4: RESTClient, HTTPClient #11992
Conversation
core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java
Outdated
Show resolved
Hide resolved
core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java
Outdated
Show resolved
Hide resolved
core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java
Outdated
Show resolved
Hide resolved
core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java
Outdated
Show resolved
Hide resolved
core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java
Outdated
Show resolved
Hide resolved
core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java
Outdated
Show resolved
Hide resolved
core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java
Outdated
Show resolved
Hide resolved
core/src/test/java/org/apache/iceberg/rest/TestRESTCatalog.java
Outdated
Show resolved
Hide resolved
this.mapper = objectMapper; | ||
this.authSession = AuthSession.EMPTY; |
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 think defaulting to empty opens up a risk that requests will be made without an explicit session. This somewhat hides the case where we missed a call that should have auth. I would think we would leave this a null and require that a session be used (e.g. validate the auth was set in the execute
call). Thoughts @nastra?
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 think that makes sense to enforce/validate that a proper auth session is being used
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.
We are going to run into a chicken and egg problem: to create an auth session I need an HTTP client; but now, to create an HTTP client, I would need an 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.
I understand that may seem like the case here, but I think we're just saying that you create it initially as set to null
, but validate when execute
is called that it is not null. You can still create the HTTPClient, but you can't make a request unless there is an active auth session. This just prevents unintentional use of an "empty" auth session (it needs to be intentional).
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.
Ah, now I get it. Let me try your suggestion.
.build()))); | ||
id -> { | ||
RESTClient client = | ||
httpClient().withAuthSession(org.apache.iceberg.rest.auth.AuthSession.EMPTY); |
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.
wouldn't this create a new http client every time this is called?
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.
Indeed. The child client is lightweight though, but it's probably better to move this call to httpClient()
.
@@ -192,7 +192,8 @@ private RESTClient httpClient() { | |||
HTTPClient.builder(properties()) | |||
.uri(baseSignerUri()) | |||
.withObjectMapper(S3ObjectMapper.mapper()) | |||
.build(); | |||
.build() | |||
.withAuthSession(org.apache.iceberg.rest.auth.AuthSession.EMPTY); |
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 worth having a withAuthSession
on the builder class itself(similar to withObjectMapper
)
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.
Builder method added.
@@ -338,6 +339,7 @@ public SdkHttpFullRequest sign( | |||
Consumer<Map<String, String>> responseHeadersConsumer = responseHeaders::putAll; | |||
S3SignResponse s3SignResponse = | |||
httpClient() | |||
.withAuthSession(org.apache.iceberg.rest.auth.AuthSession.EMPTY) |
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 this change can be reverted now that the session is set in httpClient()
@@ -84,6 +85,7 @@ private RESTClient httpClient() { | |||
|
|||
private LoadCredentialsResponse fetchCredentials() { | |||
return httpClient() | |||
.withAuthSession(AuthSession.EMPTY) |
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 just took a quick look because I'm also working on the HttpClient code and was curious about this PR.
This might have been commented, but wouldn't it be possible to pass the AuthSession to the HttpClient during creation, maybe through a Builder? Then we could avoid adding these 'withAuthSession' calls when we use the 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.
Different sessions are used depending on the context, so it can't be restricted to a single one.
baseHeaders.forEach(allHeaders::putIfAbsent); | ||
} | ||
|
||
Preconditions.checkState(authSession != null, "no AuthSession available"); |
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.
Preconditions.checkState(authSession != null, "no AuthSession available"); | |
Preconditions.checkState(authSession != null, "Invalid auth session: null"); |
@danielcweeks @nastra is there anything else I need to change? |
I see @nastra approved. I did a new pass and it looks good to me. @danielcweeks wdyt ? |
@@ -192,6 +192,7 @@ private RESTClient httpClient() { | |||
HTTPClient.builder(properties()) | |||
.uri(baseSignerUri()) | |||
.withObjectMapper(S3ObjectMapper.mapper()) | |||
.withAuthSession(org.apache.iceberg.rest.auth.AuthSession.EMPTY) |
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.
Why are we setting this to EMPTY? In order to sign we need an auth session. Wouldn't this result in no headers/sigs being produced if a call was made without explicitly setting the auth session? We want enforce that an auth session is used, but this results in falling back to nothing.
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 agree with your suggestions for other components, but here I disagree: the HTTP client must be configured with an empty auth session because it will be used for token refreshes. If it contains, say, token t1
, and token t1
gets refreshed and exchanged for t2
, there would be a clash between the token from the HTTP client (t1
) and the one from the refreshed auth session (t2
).
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 will however move this call to the authSession()
method and add a comment to explain why an EMPTY
session is needed in this specific case.
client = | ||
HTTPClient.builder(properties) | ||
.uri(properties.get(URI)) | ||
.withAuthSession(AuthSession.EMPTY) |
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.
Same here? The default should not be EMPTY, that just cases it to fall through.
try (RESTClient initClient = | ||
clientBuilder | ||
.apply(props) | ||
.withAuthSession(org.apache.iceberg.rest.auth.AuthSession.EMPTY)) { |
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.
Same here, we shouldn't default to EMPTY
this.client = | ||
clientBuilder | ||
.apply(mergedProps) | ||
.withAuthSession(org.apache.iceberg.rest.auth.AuthSession.EMPTY); |
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.
Here as well.
I feel like there may have been a small understanding with how we handle the initial authSession. We don't want to default it to empty otherwise if someone adds a request like It's better to fail early here so that we know the issue was a lack of auth info as part of the request because we'll only get a 401/403 back without knowing that we just failed to apply auth. |
@@ -75,10 +76,13 @@ private RESTClient httpClient() { | |||
if (null == client) { | |||
synchronized (this) { | |||
if (null == client) { | |||
DefaultAuthSession authSession = | |||
DefaultAuthSession.of( | |||
HTTPHeaders.of(OAuth2Util.authHeaders(properties.get(OAuth2Properties.TOKEN)))); |
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.
@adutra It's beyond the scope of this PR since this is an existing problem, but somehow we need the AuthManager/AuthSession to be able to refresh OAuth tokens here as well. The token here will expire at which point the credential provider will not be able to get new vended credentials.
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 don't mind looking into it once we have the whole API merged 👍
4th PR for the Auth Manager API. Previous ones:
This PR introduces the required changes to
RESTClient
andHTTPClient
. It also introduces aBaseHTTPClient
abstract class to facilitate the creation and execution ofHTTPRequest
s in a consistent way.The biggest change in actually in
TestRESTCatalog
, because almost everyMockito.verify()
call needs to be adapted.The AuthManager API is still "unplugged" at this point.
\cc @nastra @danielcweeks