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

feat: Support querying S2A Addresses from MDS #1400

Open
wants to merge 45 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
c96cb4a
utils.
rmehta19 Feb 23, 2024
f90be0b
formatted.
rmehta19 Feb 29, 2024
0c64a0a
static mtls config.
rmehta19 Mar 12, 2024
993663d
update autoconfig endpoint URL.
rmehta19 Mar 13, 2024
0f96e86
plaintext and mtls S2A address.
rmehta19 Mar 13, 2024
3d68cef
utils.
rmehta19 Feb 23, 2024
6d75a4e
formatted.
rmehta19 Feb 29, 2024
d932e0c
static mtls config.
rmehta19 Mar 12, 2024
6aa071b
update autoconfig endpoint URL.
rmehta19 Mar 13, 2024
ddac7aa
plaintext and mtls S2A address.
rmehta19 Mar 13, 2024
2c26736
Merge remote-tracking branch 'refs/remotes/origin/s2a-java-integratio…
rmehta19 May 17, 2024
67f9462
Use logic in ComputeEngineCredentials to get MDS URL.
rmehta19 May 17, 2024
36d4cd1
retry MDS request.
rmehta19 May 17, 2024
fc2b246
Merge branch 'main' into s2a-java-integration
rmehta19 Sep 26, 2024
359fd43
rebranch MtlsConfig as S2AConfig.
rmehta19 Sep 26, 2024
bce602e
change naming to S2AConfig elsewhere.
rmehta19 Sep 26, 2024
32caef5
set config in constructor.
rmehta19 Sep 26, 2024
b82790a
make error message more specific.
rmehta19 Sep 26, 2024
05aa9cc
move creation of transportFactory and parser out of loop.
rmehta19 Sep 26, 2024
1466f0d
construct request once.
rmehta19 Oct 2, 2024
be1cfd2
move declare to loop.
rmehta19 Oct 2, 2024
c89b56c
resolve merge conflict + add licenses.
rmehta19 Oct 23, 2024
544d9d1
remove unnecessary empty constructor.
rmehta19 Oct 23, 2024
c3ede1d
Use default retry value.
rmehta19 Oct 23, 2024
8238d50
set config in constructor.
rmehta19 Oct 23, 2024
36ab0a9
make MDS MTLS autoconfig endpoint a static constant.
rmehta19 Oct 23, 2024
36a0ac7
make S2AConfig private.
rmehta19 Oct 23, 2024
ae545c8
make constants package private.
rmehta19 Oct 24, 2024
47b3f2e
Use Builder pattern.
rmehta19 Oct 24, 2024
fb577a1
Improve javadoc.
rmehta19 Oct 24, 2024
1f333b4
Do not retry if autoconfig endpoint doesn't exist.
rmehta19 Oct 24, 2024
0bbd320
add comment around catching IOException.
rmehta19 Oct 24, 2024
0e6f5ce
Try and parse each address returned from MDS.
rmehta19 Oct 24, 2024
e786886
update license dates on added files.
rmehta19 Oct 24, 2024
12b248d
Use Google Java Http client built in retry.
rmehta19 Oct 25, 2024
4d05638
Explain why no format check.
rmehta19 Oct 25, 2024
7447f0b
run linter.
rmehta19 Oct 25, 2024
ed681f5
move it all into 1 try block.
rmehta19 Oct 25, 2024
20825f7
MockMetadataServerTransport populate content on 200.
rmehta19 Oct 25, 2024
594df7b
MockMetadataServerTransport uses s2aContentMap.
rmehta19 Oct 25, 2024
16fd964
Run mvn fmt:format.
rmehta19 Oct 25, 2024
1e6c058
Use ImmutableMap.
rmehta19 Oct 25, 2024
934679c
update javadoc to reference AIP.
rmehta19 Oct 28, 2024
257ed12
Merge branch 'main' into s2a-java-integration
rmehta19 Oct 28, 2024
0e1631a
Merge branch 'main' into s2a-java-integration
rmehta19 Oct 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
196 changes: 196 additions & 0 deletions oauth2_http/java/com/google/auth/oauth2/S2A.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* Copyright 2024, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.auth.oauth2;
zhumin8 marked this conversation as resolved.
Show resolved Hide resolved

import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpBackOffIOExceptionHandler;
import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler;
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse;
import com.google.api.client.json.JsonObjectParser;
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.client.util.GenericData;
import com.google.auth.http.HttpTransportFactory;
import com.google.common.collect.Iterables;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashSet;
import java.util.ServiceLoader;
import java.util.Set;
import javax.annotation.concurrent.ThreadSafe;

/**
* Utilities to fetch the S2A (Secure Session Agent) address from the mTLS configuration.
*
* <p>mTLS configuration is queried from the MDS MTLS Autoconfiguration endpoint.
*/
@ThreadSafe
public final class S2A {
static final String S2A_PLAINTEXT_ADDRESS_JSON_KEY = "plaintext_address";
static final String S2A_MTLS_ADDRESS_JSON_KEY = "mtls_address";
static final String S2A_CONFIG_ENDPOINT_POSTFIX =
"/computeMetadata/v1/instance/platform-security/auto-mtls-configuration";

static final String METADATA_FLAVOR = "Metadata-Flavor";
static final String GOOGLE = "Google";
private static final Set<Integer> RETRYABLE_STATUS_CODES =
new HashSet<>(Arrays.asList(500, 502, 503));
private static final String PARSE_ERROR_S2A = "Error parsing S2A Config from MDS JSON response.";
private static final String MDS_MTLS_ENDPOINT =
ComputeEngineCredentials.getMetadataServerUrl() + S2A_CONFIG_ENDPOINT_POSTFIX;

private S2AConfig config;

private transient HttpTransportFactory transportFactory;

S2A(S2A.Builder builder) {
this.transportFactory = builder.getHttpTransportFactory();
this.config = getS2AConfigFromMDS();
}

/** @return the mTLS S2A Address from the mTLS config. */
public String getMtlsS2AAddress() {
return config.getMtlsAddress();
}

/** @return the plaintext S2A Address from the mTLS config. */
public String getPlaintextS2AAddress() {
return config.getPlaintextAddress();
}

public static Builder newBuilder() {
return new Builder();
}

public static class Builder {
private HttpTransportFactory transportFactory;

protected Builder() {}

@CanIgnoreReturnValue
public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) {
this.transportFactory = transportFactory;
return this;
}

public HttpTransportFactory getHttpTransportFactory() {
return this.transportFactory;
}

public S2A build() {
return new S2A(this);
}
}

/**
* Queries the MDS mTLS Autoconfiguration endpoint and returns the {@link S2AConfig}.
*
* <p>Returns {@link S2AConfig}. If S2A is not running, or if any error occurs when making the
* request to MDS / processing the response, {@link S2AConfig} will be populated with empty
* addresses.
*
* <p>Users are expected to try to fetch the mTLS-S2A address first (via {@link
* getMtlsS2AAddress}). If it is empty or they have some problem loading the mTLS-MDS credentials,
* they should then fallback to fetching the plaintext-S2A address (via {@link
* getPlaintextS2AAddress}). If the plaintext-S2A address is empty it means that an error occurred
* when talking to the MDS / processing the response or that S2A is not running in the
* environment; in either case this indicates S2A shouldn't be used.
*
* @return the {@link S2AConfig}.
*/
private S2AConfig getS2AConfigFromMDS() {
GenericUrl genericUrl = new GenericUrl(MDS_MTLS_ENDPOINT);
JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
if (transportFactory == null) {
transportFactory =
Iterables.getFirst(
ServiceLoader.load(HttpTransportFactory.class), OAuth2Utils.HTTP_TRANSPORT_FACTORY);
}

HttpRequest request;
try {
request = transportFactory.create().createRequestFactory().buildGetRequest(genericUrl);
request.setParser(parser);
request.getHeaders().set(METADATA_FLAVOR, GOOGLE);
request.setThrowExceptionOnExecuteError(false);
request.setNumberOfRetries(OAuth2Utils.DEFAULT_NUMBER_OF_RETRIES);

ExponentialBackOff backoff =
new ExponentialBackOff.Builder()
.setInitialIntervalMillis(OAuth2Utils.INITIAL_RETRY_INTERVAL_MILLIS)
.setRandomizationFactor(OAuth2Utils.RETRY_RANDOMIZATION_FACTOR)
.setMultiplier(OAuth2Utils.RETRY_MULTIPLIER)
.build();

// Retry on 5xx status codes.
request.setUnsuccessfulResponseHandler(
new HttpBackOffUnsuccessfulResponseHandler(backoff)
.setBackOffRequired(
response -> RETRYABLE_STATUS_CODES.contains(response.getStatusCode())));
request.setIOExceptionHandler(new HttpBackOffIOExceptionHandler(backoff));
} catch (IOException e) {
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.

S2AConfig.createBuilder().build() sets plainTextAddress and mtlsS2AAddress to "" by default. Can we move this code in this try block into the one below (merging them)?

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 ed681f5


String plaintextS2AAddress = "";
String mtlsS2AAddress = "";
try {
HttpResponse response = request.execute();
InputStream content = response.getContent();
Comment on lines +165 to +166
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe do try/catch block around these to be more specific about the exceptions catching? Nested try/catch blocks are a bit hard to read.

if (content == null) {
return S2AConfig.createBuilder().build();
}
lqiu96 marked this conversation as resolved.
Show resolved Hide resolved
GenericData responseData = response.parseAs(GenericData.class);
try {
plaintextS2AAddress =
OAuth2Utils.validateString(
responseData, S2A_PLAINTEXT_ADDRESS_JSON_KEY, PARSE_ERROR_S2A);
} catch (IOException ignore) {
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add comment on why these are ignored?

try {
mtlsS2AAddress =
OAuth2Utils.validateString(responseData, S2A_MTLS_ADDRESS_JSON_KEY, PARSE_ERROR_S2A);
} catch (IOException ignore) {
}
} catch (IOException ignore) {
/*
* Return empty addresses in {@link S2AConfig} once all retries have been exhausted.
*/
}

return S2AConfig.createBuilder()
.setPlaintextAddress(plaintextS2AAddress)
.setMtlsAddress(mtlsS2AAddress)
.build();
}
}
96 changes: 96 additions & 0 deletions oauth2_http/java/com/google/auth/oauth2/S2AConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright 2024, Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.google.auth.oauth2;
lqiu96 marked this conversation as resolved.
Show resolved Hide resolved

import com.google.errorprone.annotations.CanIgnoreReturnValue;

/** Holds an mTLS configuration (consists of address of S2A) retrieved from the Metadata Server. */
final class S2AConfig {
// plaintextAddress is the plaintext address to reach the S2A.
private final String plaintextAddress;

// mtlsAddress is the mTLS address to reach the S2A.
private final String mtlsAddress;

public static Builder createBuilder() {
return new Builder();
}

public String getPlaintextAddress() {

Choose a reason for hiding this comment

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

I'd like Javadocs for all these public things, pointing to public docs for MDS.

Copy link
Author

Choose a reason for hiding this comment

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

We don't have this documented in public MDS docs (e.g. https://cloud.google.com/compute/docs/metadata/predefined-metadata-keys). We do have an AIP: https://google.aip.dev/auth/4115 which discusses this autconfig endpoint and how it fits in the mTLS via S2A + bound tokens story. WDYT about 934679c?

return plaintextAddress;
}

public String getMtlsAddress() {
return mtlsAddress;
}

public static final class Builder {
// plaintextAddress is the plaintext address to reach the S2A.
private String plaintextAddress;

// mtlsAddress is the mTLS address to reach the S2A.
private String mtlsAddress;

Builder() {
plaintextAddress = "";
mtlsAddress = "";
}

@CanIgnoreReturnValue
public Builder setPlaintextAddress(String plaintextAddress) {
/*
* No validation / format check is necessary here. It is up to the client which consumes this address
* to return error if there is a problem connecting to S2A at that address.
*/
this.plaintextAddress = plaintextAddress;
Copy link
Contributor

Choose a reason for hiding this comment

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

Does these addresses need any validation or format check when setting with builder?

Copy link
Author

Choose a reason for hiding this comment

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

No validation/format check is necessary here, because we own the MDS endpoint that is being queried to get the address, and it is up to the client which consumes the address (S2A client) to return error if there is a problem connecting to the S2A at that address.

Copy link
Contributor

Choose a reason for hiding this comment

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

Can you add this explanation as javadoc comment here or to getS2AConfigFromMDS() for future references?

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 4d05638

return this;
}

@CanIgnoreReturnValue
public Builder setMtlsAddress(String mtlsAddress) {
/*
* No validation / format check is necessary here. It is up to the client which consumes this address
* to return error if there is a problem connecting to S2A at that address.
*/
this.mtlsAddress = mtlsAddress;
return this;
}

public S2AConfig build() {
return new S2AConfig(plaintextAddress, mtlsAddress);
}
}

private S2AConfig(String plaintextAddress, String mtlsAddress) {
this.plaintextAddress = plaintextAddress;
this.mtlsAddress = mtlsAddress;
}
}
Loading