-
Notifications
You must be signed in to change notification settings - Fork 229
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Support querying S2A Addresses from MDS (#1400)
* utils. * formatted. * static mtls config. * update autoconfig endpoint URL. * plaintext and mtls S2A address. * utils. * formatted. * static mtls config. * update autoconfig endpoint URL. * plaintext and mtls S2A address. * Use logic in ComputeEngineCredentials to get MDS URL. * retry MDS request. * rebranch MtlsConfig as S2AConfig. * change naming to S2AConfig elsewhere. * set config in constructor. * make error message more specific. * move creation of transportFactory and parser out of loop. * construct request once. * move declare to loop. * remove unnecessary empty constructor. * Use default retry value. * set config in constructor. * make MDS MTLS autoconfig endpoint a static constant. * make S2AConfig private. * make constants package private. * Use Builder pattern. * Improve javadoc. * Do not retry if autoconfig endpoint doesn't exist. * add comment around catching IOException. * Try and parse each address returned from MDS. * update license dates on added files. * Use Google Java Http client built in retry. * Explain why no format check. * run linter. * move it all into 1 try block. * MockMetadataServerTransport populate content on 200. * MockMetadataServerTransport uses s2aContentMap. * Run mvn fmt:format. * Use ImmutableMap. * update javadoc to reference AIP. * Don't nest try/catch + add some comments about why no throw errors. * update javadoc for each public method. * add experimental note. * format.
- Loading branch information
Showing
5 changed files
with
567 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
/* | ||
* 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; | ||
|
||
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. See | ||
* https://google.aip.dev/auth/4115 for details. | ||
* | ||
* <p>This is an experimental utility. | ||
*/ | ||
@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() { | ||
if (transportFactory == null) { | ||
transportFactory = | ||
Iterables.getFirst( | ||
ServiceLoader.load(HttpTransportFactory.class), OAuth2Utils.HTTP_TRANSPORT_FACTORY); | ||
} | ||
|
||
HttpRequest request = null; | ||
GenericUrl genericUrl = new GenericUrl(MDS_MTLS_ENDPOINT); | ||
try { | ||
request = transportFactory.create().createRequestFactory().buildGetRequest(genericUrl); | ||
} catch (IOException ignore) { | ||
/* | ||
* Return empty addresses in {@link S2AConfig} if error building the GET request. | ||
*/ | ||
return S2AConfig.createBuilder().build(); | ||
} | ||
|
||
request.setParser(new JsonObjectParser(OAuth2Utils.JSON_FACTORY)); | ||
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)); | ||
|
||
GenericData responseData = null; | ||
try { | ||
HttpResponse response = request.execute(); | ||
InputStream content = response.getContent(); | ||
if (content == null) { | ||
return S2AConfig.createBuilder().build(); | ||
} | ||
responseData = response.parseAs(GenericData.class); | ||
} catch (IOException ignore) { | ||
/* | ||
* Return empty addresses in {@link S2AConfig} once all retries have been exhausted. | ||
*/ | ||
return S2AConfig.createBuilder().build(); | ||
} | ||
|
||
String plaintextS2AAddress = ""; | ||
String mtlsS2AAddress = ""; | ||
try { | ||
plaintextS2AAddress = | ||
OAuth2Utils.validateString(responseData, S2A_PLAINTEXT_ADDRESS_JSON_KEY, PARSE_ERROR_S2A); | ||
} catch (IOException ignore) { | ||
/* | ||
* Do not throw error because of parsing error, just leave the address as empty in {@link S2AConfig}. | ||
*/ | ||
} | ||
try { | ||
mtlsS2AAddress = | ||
OAuth2Utils.validateString(responseData, S2A_MTLS_ADDRESS_JSON_KEY, PARSE_ERROR_S2A); | ||
} catch (IOException ignore) { | ||
/* | ||
* Do not throw error because of parsing error, just leave the address as empty in {@link S2AConfig}. | ||
*/ | ||
} | ||
|
||
return S2AConfig.createBuilder() | ||
.setPlaintextAddress(plaintextS2AAddress) | ||
.setMtlsAddress(mtlsS2AAddress) | ||
.build(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
/* | ||
* 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; | ||
|
||
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(); | ||
} | ||
|
||
/** @return the plaintext S2A Address. */ | ||
public String getPlaintextAddress() { | ||
return plaintextAddress; | ||
} | ||
|
||
/** @return the mTLS S2A Address. */ | ||
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; | ||
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; | ||
} | ||
} |
Oops, something went wrong.