diff --git a/oauth2_http/java/com/google/auth/oauth2/S2A.java b/oauth2_http/java/com/google/auth/oauth2/S2A.java
new file mode 100644
index 000000000..aecf4a3b2
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/S2A.java
@@ -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.
+ *
+ *
mTLS configuration is queried from the MDS MTLS Autoconfiguration endpoint. See
+ * https://google.aip.dev/auth/4115 for details.
+ *
+ *
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 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}.
+ *
+ * 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.
+ *
+ *
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();
+ }
+}
diff --git a/oauth2_http/java/com/google/auth/oauth2/S2AConfig.java b/oauth2_http/java/com/google/auth/oauth2/S2AConfig.java
new file mode 100644
index 000000000..90765f096
--- /dev/null
+++ b/oauth2_http/java/com/google/auth/oauth2/S2AConfig.java
@@ -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;
+ }
+}
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java b/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java
index d21491027..5a854480c 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java
@@ -39,6 +39,7 @@
import com.google.api.client.testing.http.MockLowLevelHttpRequest;
import com.google.api.client.testing.http.MockLowLevelHttpResponse;
import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableMap;
import com.google.common.io.BaseEncoding;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
@@ -62,6 +63,9 @@ public class MockMetadataServerTransport extends MockHttpTransport {
private byte[] signature;
+ private Map s2aContentMap = new HashMap<>();
+
+ private boolean emptyContent;
private MockLowLevelHttpRequest request;
public MockMetadataServerTransport() {}
@@ -103,6 +107,14 @@ public void setIdToken(String idToken) {
this.idToken = idToken;
}
+ public void setS2AContentMap(ImmutableMap map) {
+ this.s2aContentMap = map;
+ }
+
+ public void setEmptyContent(boolean emptyContent) {
+ this.emptyContent = emptyContent;
+ }
+
public MockLowLevelHttpRequest getRequest() {
return request;
}
@@ -121,6 +133,8 @@ public LowLevelHttpRequest buildRequest(String method, String url) throws IOExce
} else if (isIdentityDocumentUrl(url)) {
this.request = getMockRequestForIdentityDocument(url);
return this.request;
+ } else if (isMtlsConfigRequestUrl(url)) {
+ return getMockRequestForMtlsConfig(url);
}
this.request =
new MockLowLevelHttpRequest(url) {
@@ -272,6 +286,40 @@ public LowLevelHttpResponse execute() throws IOException {
};
}
+ private MockLowLevelHttpRequest getMockRequestForMtlsConfig(String url) {
+ return new MockLowLevelHttpRequest(url) {
+ @Override
+ public LowLevelHttpResponse execute() throws IOException {
+
+ String metadataRequestHeader = getFirstHeaderValue(S2A.METADATA_FLAVOR);
+ if (!S2A.GOOGLE.equals(metadataRequestHeader)) {
+ throw new IOException("Metadata request header not found");
+ }
+
+ // Create the JSON response
+ GenericJson content = new GenericJson();
+ content.setFactory(OAuth2Utils.JSON_FACTORY);
+ if (requestStatusCode == 200) {
+ for (Map.Entry entrySet : s2aContentMap.entrySet()) {
+ content.put(entrySet.getKey(), entrySet.getValue());
+ }
+ }
+ String contentText = content.toPrettyString();
+
+ MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
+
+ if (requestStatusCode != null) {
+ response.setStatusCode(requestStatusCode);
+ }
+ if (emptyContent == true) {
+ return response.setZeroContent();
+ }
+ response.setContentType(Json.MEDIA_TYPE).setContent(contentText);
+ return response;
+ }
+ };
+ }
+
protected boolean isGetServiceAccountsUrl(String url) {
return url.equals(ComputeEngineCredentials.getServiceAccountsUrl());
}
@@ -285,4 +333,10 @@ protected boolean isSignRequestUrl(String url) {
protected boolean isIdentityDocumentUrl(String url) {
return url.startsWith(String.format(ComputeEngineCredentials.getIdentityDocumentUrl()));
}
+
+ protected boolean isMtlsConfigRequestUrl(String url) {
+ return url.equals(
+ String.format(
+ ComputeEngineCredentials.getMetadataServerUrl() + S2A.S2A_CONFIG_ENDPOINT_POSTFIX));
+ }
}
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/S2AConfigTest.java b/oauth2_http/javatests/com/google/auth/oauth2/S2AConfigTest.java
new file mode 100644
index 000000000..cd8cca899
--- /dev/null
+++ b/oauth2_http/javatests/com/google/auth/oauth2/S2AConfigTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test cases for {@link S2AConfig}. */
+@RunWith(JUnit4.class)
+public class S2AConfigTest {
+ private static final String S2A_PLAINTEXT_ADDRESS = "plaintext";
+ private static final String S2A_MTLS_ADDRESS = "mtls";
+
+ @Test
+ public void createS2AConfig_success() {
+ S2AConfig config =
+ S2AConfig.createBuilder()
+ .setPlaintextAddress(S2A_PLAINTEXT_ADDRESS)
+ .setMtlsAddress(S2A_MTLS_ADDRESS)
+ .build();
+ assertEquals(S2A_PLAINTEXT_ADDRESS, config.getPlaintextAddress());
+ assertEquals(S2A_MTLS_ADDRESS, config.getMtlsAddress());
+ }
+
+ @Test
+ public void createEmptyS2AConfig_success() {
+ S2AConfig config = S2AConfig.createBuilder().build();
+ assertTrue(config.getPlaintextAddress().isEmpty());
+ assertTrue(config.getMtlsAddress().isEmpty());
+ }
+}
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/S2ATest.java b/oauth2_http/javatests/com/google/auth/oauth2/S2ATest.java
new file mode 100644
index 000000000..f532a2727
--- /dev/null
+++ b/oauth2_http/javatests/com/google/auth/oauth2/S2ATest.java
@@ -0,0 +1,142 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.api.client.http.HttpStatusCodes;
+import com.google.auth.oauth2.ComputeEngineCredentialsTest.MockMetadataServerTransportFactory;
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test cases for {@link S2A}. */
+@RunWith(JUnit4.class)
+public class S2ATest {
+
+ private static final String INVALID_JSON_KEY = "invalid_key";
+ private static final String S2A_PLAINTEXT_ADDRESS = "plaintext";
+ private static final String S2A_MTLS_ADDRESS = "mtls";
+
+ @Test
+ public void getS2AAddress_validAddress() {
+ MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
+ transportFactory.transport.setS2AContentMap(
+ ImmutableMap.of(
+ S2A.S2A_PLAINTEXT_ADDRESS_JSON_KEY,
+ S2A_PLAINTEXT_ADDRESS,
+ S2A.S2A_MTLS_ADDRESS_JSON_KEY,
+ S2A_MTLS_ADDRESS));
+ transportFactory.transport.setRequestStatusCode(HttpStatusCodes.STATUS_CODE_OK);
+
+ S2A s2aUtils = S2A.newBuilder().setHttpTransportFactory(transportFactory).build();
+ String plaintextS2AAddress = s2aUtils.getPlaintextS2AAddress();
+ String mtlsS2AAddress = s2aUtils.getMtlsS2AAddress();
+ assertEquals(S2A_PLAINTEXT_ADDRESS, plaintextS2AAddress);
+ assertEquals(S2A_MTLS_ADDRESS, mtlsS2AAddress);
+ }
+
+ @Test
+ public void getS2AAddress_queryEndpointResponseErrorCode_emptyAddress() {
+ MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
+ transportFactory.transport.setS2AContentMap(
+ ImmutableMap.of(
+ S2A.S2A_PLAINTEXT_ADDRESS_JSON_KEY,
+ S2A_PLAINTEXT_ADDRESS,
+ S2A.S2A_MTLS_ADDRESS_JSON_KEY,
+ S2A_MTLS_ADDRESS));
+ transportFactory.transport.setRequestStatusCode(
+ HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE);
+
+ S2A s2aUtils = S2A.newBuilder().setHttpTransportFactory(transportFactory).build();
+ String plaintextS2AAddress = s2aUtils.getPlaintextS2AAddress();
+ String mtlsS2AAddress = s2aUtils.getMtlsS2AAddress();
+ assertTrue(plaintextS2AAddress.isEmpty());
+ assertTrue(mtlsS2AAddress.isEmpty());
+ }
+
+ @Test
+ public void getS2AAddress_queryEndpointResponseEmpty_emptyAddress() {
+ MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
+ transportFactory.transport.setS2AContentMap(
+ ImmutableMap.of(
+ S2A.S2A_PLAINTEXT_ADDRESS_JSON_KEY,
+ S2A_PLAINTEXT_ADDRESS,
+ S2A.S2A_MTLS_ADDRESS_JSON_KEY,
+ S2A_MTLS_ADDRESS));
+ transportFactory.transport.setRequestStatusCode(HttpStatusCodes.STATUS_CODE_OK);
+ transportFactory.transport.setEmptyContent(true);
+
+ S2A s2aUtils = S2A.newBuilder().setHttpTransportFactory(transportFactory).build();
+ String plaintextS2AAddress = s2aUtils.getPlaintextS2AAddress();
+ String mtlsS2AAddress = s2aUtils.getMtlsS2AAddress();
+ assertTrue(plaintextS2AAddress.isEmpty());
+ assertTrue(mtlsS2AAddress.isEmpty());
+ }
+
+ @Test
+ public void getS2AAddress_queryEndpointResponseInvalidPlaintextJsonKey_plaintextEmptyAddress() {
+ MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
+ transportFactory.transport.setS2AContentMap(
+ ImmutableMap.of(
+ INVALID_JSON_KEY,
+ S2A_PLAINTEXT_ADDRESS,
+ S2A.S2A_MTLS_ADDRESS_JSON_KEY,
+ S2A_MTLS_ADDRESS));
+ transportFactory.transport.setRequestStatusCode(HttpStatusCodes.STATUS_CODE_OK);
+
+ S2A s2aUtils = S2A.newBuilder().setHttpTransportFactory(transportFactory).build();
+ String plaintextS2AAddress = s2aUtils.getPlaintextS2AAddress();
+ String mtlsS2AAddress = s2aUtils.getMtlsS2AAddress();
+ assertTrue(plaintextS2AAddress.isEmpty());
+ assertEquals(S2A_MTLS_ADDRESS, mtlsS2AAddress);
+ }
+
+ @Test
+ public void getS2AAddress_queryEndpointResponseInvalidMtlsJsonKey_mtlsEmptyAddress() {
+ MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
+ transportFactory.transport.setS2AContentMap(
+ ImmutableMap.of(
+ S2A.S2A_PLAINTEXT_ADDRESS_JSON_KEY,
+ S2A_PLAINTEXT_ADDRESS,
+ INVALID_JSON_KEY,
+ S2A_MTLS_ADDRESS));
+ transportFactory.transport.setRequestStatusCode(HttpStatusCodes.STATUS_CODE_OK);
+
+ S2A s2aUtils = S2A.newBuilder().setHttpTransportFactory(transportFactory).build();
+ String plaintextS2AAddress = s2aUtils.getPlaintextS2AAddress();
+ String mtlsS2AAddress = s2aUtils.getMtlsS2AAddress();
+ assertEquals(S2A_PLAINTEXT_ADDRESS, plaintextS2AAddress);
+ assertTrue(mtlsS2AAddress.isEmpty());
+ }
+}