Skip to content
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

Query S2A Address from MDS #1400

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

rmehta19
Copy link

@rmehta19 rmehta19 commented May 9, 2024

Add utility to get S2A address from MDS MTLS autoconfiguration endpoint.

This utility will be used when creating mTLS channel using S2A Java Client, which takes S2A Address as input to create S2AChannelCredentials.

Parallel change in go: googleapis/google-api-go-client#1874
S2A Java client: grpc/grpc-java#11113

@rmehta19 rmehta19 requested a review from a team as a code owner May 9, 2024 00:20
@product-auto-label product-auto-label bot added the size: l Pull request size is large. label May 9, 2024
@rmehta19
Copy link
Author

cc: @xmenxk

oauth2_http/java/com/google/auth/oauth2/S2A.java Outdated Show resolved Hide resolved
oauth2_http/java/com/google/auth/oauth2/S2A.java Outdated Show resolved Hide resolved
oauth2_http/java/com/google/auth/oauth2/S2A.java Outdated Show resolved Hide resolved
Comment on lines 67 to 89
if (transportFactory == null) {
transportFactory =
Iterables.getFirst(
ServiceLoader.load(HttpTransportFactory.class), OAuth2Utils.HTTP_TRANSPORT_FACTORY);
}
String url = getMdsMtlsEndpoint();
GenericUrl genericUrl = new GenericUrl(url);
HttpRequest request =
transportFactory.create().createRequestFactory().buildGetRequest(genericUrl);
JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
request.setParser(parser);
request.getHeaders().set(METADATA_FLAVOR, GOOGLE);
request.setThrowExceptionOnExecuteError(false);
HttpResponse response = request.execute();

if (!response.isSuccessStatusCode()) {
return MtlsConfig.createBuilder().build();
}

InputStream content = response.getContent();
if (content == null) {
return MtlsConfig.createBuilder().build();
}
Copy link

Choose a reason for hiding this comment

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

is it possible to reuse the code below for querying mds endpoint?

private HttpResponse getMetadataResponse(String url) throws IOException {

Copy link
Author

Choose a reason for hiding this comment

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

We could use this function to get the MDS HttpResponse, but it would only replace a few lines (~7) in this function:

creating the HttpRequest and executing it to get the response, replaced with

response = getMetadataResponse(url).

However, in order to do this, we would also have to:

  • ignore the ComputeEngineCredentials specific errors thrown by the function, because we only care if we are able to successfully create a response (aligning with Go implementation, return empty S2A Address if any error). This technically works, although I am not sure it is best practice.
  • getMetadataResponse(String url) is not static, so we would have to create an instance of ComputeEngineCredentials to use it.

WDYT?

Copy link

sonarcloud bot commented May 17, 2024

@rmehta19
Copy link
Author

@westarle, friendly ping :). Please review when you get a chance.

import com.google.errorprone.annotations.CanIgnoreReturnValue;

/** Holds an mTLS configuration (consists of address of S2A) retrieved from the Metadata Server. */
public final class MtlsConfig {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this should be re-branded to S2AConfig. MtlsConfig is much more generic than the settings in this class.

Copy link
Author

Choose a reason for hiding this comment

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

Done in 359fd43

Comment on lines 25 to 28
public static final String METADATA_FLAVOR = "Metadata-Flavor";
public static final String GOOGLE = "Google";
private static final int MAX_MDS_PING_TRIES = 3;
private static final String PARSE_ERROR_S2A = "Error parsing Mtls Auto Config response.";
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there any existing code for these constants?

Copy link
Author

Choose a reason for hiding this comment

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

Some are defined in ComputeEngineCredentials.

*/
@ThreadSafe
public final class S2A {
public static final String MTLS_CONFIG_ENDPOINT =
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public static final String MTLS_CONFIG_ENDPOINT =
public static final String S2A_CONFIG_ENDPOINT_POSTFIX =

Copy link
Author

Choose a reason for hiding this comment

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

Done in bce602e

public static final String METADATA_FLAVOR = "Metadata-Flavor";
public static final String GOOGLE = "Google";
private static final int MAX_MDS_PING_TRIES = 3;
private static final String PARSE_ERROR_S2A = "Error parsing Mtls Auto Config response.";
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this error message should be expanded to explain what went wrong and how it can be triaged.

Copy link
Author

Choose a reason for hiding this comment

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

Done in b82790a

Comment on lines 42 to 45
if (config == null) {
config = getMdsMtlsConfig();
}
return config.getMtlsS2AAddress();
Copy link
Contributor

Choose a reason for hiding this comment

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

Could a valid config be a requirement to instantiate this class?

Copy link
Author

Choose a reason for hiding this comment

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

The behavior we would like is for the S2A config to be queried once from the MDS, all subsequent requests from the application for the S2A config should return the cached config.
Re: Could a valid config be a requirement to instantiate this class

I think we can do this and perhaps simplify the code. I moved the call to get the config to the constructor.

32caef5

*
* @return the {@link MtlsConfig}.
*/
private MtlsConfig getMdsMtlsConfig() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
private MtlsConfig getMdsMtlsConfig() {
private MtlsConfig getS2AConfigFromMDS() {

Copy link
Author

Choose a reason for hiding this comment

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

Done in bce602e

Comment on lines 72 to 76
if (transportFactory == null) {
transportFactory =
Iterables.getFirst(
ServiceLoader.load(HttpTransportFactory.class), OAuth2Utils.HTTP_TRANSPORT_FACTORY);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be outside of the loop?

Copy link
Author

Choose a reason for hiding this comment

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

Done in 05aa9cc

}
HttpRequest request =
transportFactory.create().createRequestFactory().buildGetRequest(genericUrl);
JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be outside of the loop?

Copy link
Author

Choose a reason for hiding this comment

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

Done in 05aa9cc

.setMtlsS2AAddress(mtlsS2AAddress)
.build();
}
return MtlsConfig.createBuilder().build();
Copy link
Contributor

Choose a reason for hiding this comment

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

How will callers know if a valid config is returned?

Copy link
Author

Choose a reason for hiding this comment

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

If the plaintext and mTLS S2A addresses are able to be parsed from the MDS response, it is a valid config. The MDS MTLS Autoconfig endpoint always returns the address that S2A is running at, if available. It is the caller's responsibility to check if the address is empty before passing it on the S2A Client to create ChannelCredentials (example). This is similar to what we do in Go: https://github.com/googleapis/google-api-go-client/blob/main/internal/s2a.go#L22-L31

@rmehta19 rmehta19 requested a review from a team as a code owner September 26, 2024 21:03
Copy link

sonarcloud bot commented Sep 26, 2024

Comment on lines 73 to 77
HttpRequest request =
transportFactory.create().createRequestFactory().buildGetRequest(genericUrl);
request.setParser(parser);
request.getHeaders().set(METADATA_FLAVOR, GOOGLE);
request.setThrowExceptionOnExecuteError(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can the request be re-used across loops?

Not familiar with Java, but I imagine it's re-usable / expensive to create on each loop.

Copy link
Author

@rmehta19 rmehta19 Oct 2, 2024

Choose a reason for hiding this comment

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

Done in 1466f0d

Comment on lines 59 to 60
String plaintextS2AAddress = "";
String mtlsS2AAddress = "";
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Why declare these at the top? Is it a code style choice?

Copy link
Author

Choose a reason for hiding this comment

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

It could be declared in the loop, moved in be1cfd2

.setMtlsAddress(mtlsS2AAddress)
.build();
}
return S2AConfig.createBuilder().build();
Copy link
Contributor

Choose a reason for hiding this comment

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

So consumers will have to check if the config contains empty strings? Could you use a result type or other form of indication that you were unable to build a valid config?

Copy link
Author

Choose a reason for hiding this comment

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

This was done to keep consistency with Go implementation: https://github.com/googleapis/google-api-go-client/blob/main/internal/s2a.go#L22

The rationale is that we do not want to throw an error from this API that the client has to handle. If any problem occurs querying the endpoint for the address, we return an empty address. The client can pass that empty address to the S2A Client libraries, which will throw an error (e.g. something like "Error: Empty S2A Address.").

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size: l Pull request size is large.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants