Skip to content

Commit 1138ca6

Browse files
rmehta19blakeli0
andauthoredNov 14, 2024··
feat: Add experimental S2A integration in client libraries grpc transport (#3326)
Modify the Client Libraries gRPC Channel builder to use mTLS via S2A if the experimental environment variable is set, S2A is available (We check this by using utility added in googleapis/google-auth-library-java#1400), and a few more conditions (see `shouldUseS2A`). Following https://google.aip.dev/auth/4115, Only attempt to use S2A after DirectPath and DCA (https://google.aip.dev/auth/4114) are ruled out as options. If conditions to use S2A are not met (env variable not set, or S2A is not running in environment, etc (`shouldUseS2A` returns false)), fall back to default TLS connection. When we are creating S2A-enabled Grpc Channel Credentials, we first try to secure the connection between the client and the S2A via MTLS, using [MTLS-MDS](https://cloud.google.com/compute/docs/metadata/overview#https-mds) credentials. If MTLS-MDS credentials can't be loaded, then we fallback to a plaintext connection between the client and S2A. The parallel go implementation : googleapis/google-api-go-client#1874 (now lives here: https://github.com/googleapis/google-cloud-go/blob/main/auth/internal/transport/cba.go) S2A Java client: https://github.com/grpc/grpc-java/tree/master/s2a Resolving b/376258193 means that S2A.java is no longer experimental --------- Co-authored-by: blakeli <blakeli@google.com>
1 parent b20624c commit 1138ca6

20 files changed

+570
-4
lines changed
 

‎WORKSPACE

-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ maven_install(
6565
"com.google.api:gapic-generator-java:" + _gapic_generator_java_version,
6666
] + PROTOBUF_MAVEN_ARTIFACTS + IO_GRPC_GRPC_JAVA_ARTIFACTS,
6767
fail_on_missing_checksum = False,
68-
override_targets = IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS,
6968
repositories = [
7069
"m2Local",
7170
"https://repo.maven.apache.org/maven2/",

‎gax-java/dependencies.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ version.io_grpc=1.68.1
3737
# 2) Replace all characters which are neither alphabetic nor digits with the underscore ('_') character
3838
maven.com_google_api_grpc_proto_google_common_protos=com.google.api.grpc:proto-google-common-protos:2.46.0
3939
maven.com_google_api_grpc_grpc_google_common_protos=com.google.api.grpc:grpc-google-common-protos:2.46.0
40-
maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.29.0
40+
maven.com_google_auth_google_auth_library_oauth2_http=com.google.auth:google-auth-library-oauth2-http:1.30.0
4141
maven.com_google_auth_google_auth_library_credentials=com.google.auth:google-auth-library-credentials:1.30.0
4242
maven.io_opentelemetry_opentelemetry_api=io.opentelemetry:opentelemetry-api:1.42.1
4343
maven.io_opencensus_opencensus_api=io.opencensus:opencensus-api:0.31.1

‎gax-java/gax-grpc/BUILD.bazel

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ _COMPILE_DEPS = [
2828
"@io_grpc_grpc_netty_shaded//jar",
2929
"@io_grpc_grpc_grpclb//jar",
3030
"@io_grpc_grpc_java//alts:alts",
31+
"@io_grpc_grpc_java//s2a:s2av2_credentials",
3132
"@io_netty_netty_tcnative_boringssl_static//jar",
3233
"@javax_annotation_javax_annotation_api//jar",
3334
"//gax:gax",

‎gax-java/gax-grpc/pom.xml

+4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@
6363
<groupId>io.grpc</groupId>
6464
<artifactId>grpc-protobuf</artifactId>
6565
</dependency>
66+
<dependency>
67+
<groupId>io.grpc</groupId>
68+
<artifactId>grpc-s2a</artifactId>
69+
</dependency>
6670
<dependency>
6771
<groupId>io.grpc</groupId>
6872
<artifactId>grpc-stub</artifactId>

‎gax-java/gax-grpc/src/main/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProvider.java

+155-1
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,24 @@
4646
import com.google.auth.ApiKeyCredentials;
4747
import com.google.auth.Credentials;
4848
import com.google.auth.oauth2.ComputeEngineCredentials;
49+
import com.google.auth.oauth2.SecureSessionAgent;
50+
import com.google.auth.oauth2.SecureSessionAgentConfig;
4951
import com.google.common.annotations.VisibleForTesting;
5052
import com.google.common.base.Preconditions;
53+
import com.google.common.base.Strings;
5154
import com.google.common.collect.ImmutableList;
5255
import com.google.common.collect.ImmutableMap;
5356
import com.google.common.io.Files;
5457
import io.grpc.CallCredentials;
5558
import io.grpc.ChannelCredentials;
5659
import io.grpc.Grpc;
60+
import io.grpc.InsecureChannelCredentials;
5761
import io.grpc.ManagedChannel;
5862
import io.grpc.ManagedChannelBuilder;
5963
import io.grpc.TlsChannelCredentials;
6064
import io.grpc.alts.GoogleDefaultChannelCredentials;
6165
import io.grpc.auth.MoreCallCredentials;
66+
import io.grpc.s2a.S2AChannelCredentials;
6267
import java.io.File;
6368
import java.io.IOException;
6469
import java.nio.charset.StandardCharsets;
@@ -99,6 +104,15 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
99104
@VisibleForTesting
100105
static final String DIRECT_PATH_ENV_ENABLE_XDS = "GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS";
101106

107+
// The public portion of the mTLS MDS root certificate is stored for performing
108+
// cert verification when establishing an mTLS connection with the MDS. See
109+
// https://cloud.google.com/compute/docs/metadata/overview#https-mds-root-certs
110+
private static final String MTLS_MDS_ROOT_PATH = "/run/google-mds-mtls/root.crt";
111+
// The mTLS MDS credentials are formatted as the concatenation of a PEM-encoded certificate chain
112+
// followed by a PEM-encoded private key. See
113+
// https://cloud.google.com/compute/docs/metadata/overview#https-mds-client-certs
114+
private static final String MTLS_MDS_CERT_CHAIN_AND_KEY_PATH = "/run/google-mds-mtls/client.key";
115+
102116
static final long DIRECT_PATH_KEEP_ALIVE_TIME_SECONDS = 3600;
103117
static final long DIRECT_PATH_KEEP_ALIVE_TIMEOUT_SECONDS = 20;
104118
static final String GCE_PRODUCTION_NAME_PRIOR_2016 = "Google";
@@ -107,6 +121,7 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
107121
private final int processorCount;
108122
private final Executor executor;
109123
private final HeaderProvider headerProvider;
124+
private final boolean useS2A;
110125
private final String endpoint;
111126
// TODO: remove. envProvider currently provides DirectPath environment variable, and is only used
112127
// during initial rollout for DirectPath. This provider will be removed once the DirectPath
@@ -126,6 +141,7 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
126141
@Nullable private final Boolean allowNonDefaultServiceAccount;
127142
@VisibleForTesting final ImmutableMap<String, ?> directPathServiceConfig;
128143
@Nullable private final MtlsProvider mtlsProvider;
144+
@Nullable private final SecureSessionAgent s2aConfigProvider;
129145
@VisibleForTesting final Map<String, String> headersWithDuplicatesRemoved = new HashMap<>();
130146

131147
@Nullable
@@ -136,7 +152,9 @@ private InstantiatingGrpcChannelProvider(Builder builder) {
136152
this.executor = builder.executor;
137153
this.headerProvider = builder.headerProvider;
138154
this.endpoint = builder.endpoint;
155+
this.useS2A = builder.useS2A;
139156
this.mtlsProvider = builder.mtlsProvider;
157+
this.s2aConfigProvider = builder.s2aConfigProvider;
140158
this.envProvider = builder.envProvider;
141159
this.interceptorProvider = builder.interceptorProvider;
142160
this.maxInboundMessageSize = builder.maxInboundMessageSize;
@@ -225,6 +243,17 @@ public TransportChannelProvider withEndpoint(String endpoint) {
225243
return toBuilder().setEndpoint(endpoint).build();
226244
}
227245

246+
/**
247+
* Specify whether or not to use S2A.
248+
*
249+
* @param useS2A
250+
* @return A new {@link InstantiatingGrpcChannelProvider} with useS2A set.
251+
*/
252+
@Override
253+
public TransportChannelProvider withUseS2A(boolean useS2A) {
254+
return toBuilder().setUseS2A(useS2A).build();
255+
}
256+
228257
/** @deprecated Please modify pool settings via {@link #toBuilder()} */
229258
@Deprecated
230259
@Override
@@ -410,6 +439,101 @@ ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSec
410439
return null;
411440
}
412441

442+
/**
443+
* This method creates {@link TlsChannelCredentials} to be used by the client to establish an mTLS
444+
* connection to S2A. Returns null if any of {@param trustBundle}, {@param privateKey} or {@param
445+
* certChain} are missing.
446+
*
447+
* @param trustBundle the trust bundle to be used to establish the client -> S2A mTLS connection
448+
* @param privateKey the client's private key to be used to establish the client -> S2A mtls
449+
* connection
450+
* @param certChain the client's cert chain to be used to establish the client -> S2A mtls
451+
* connection
452+
* @return {@link ChannelCredentials} to use to create an mtls connection between client and S2A
453+
* @throws IOException on error
454+
*/
455+
@VisibleForTesting
456+
ChannelCredentials createMtlsToS2AChannelCredentials(
457+
File trustBundle, File privateKey, File certChain) throws IOException {
458+
if (trustBundle == null || privateKey == null || certChain == null) {
459+
return null;
460+
}
461+
return TlsChannelCredentials.newBuilder()
462+
.keyManager(privateKey, certChain)
463+
.trustManager(trustBundle)
464+
.build();
465+
}
466+
467+
/**
468+
* This method creates {@link ChannelCredentials} to be used by client to establish a plaintext
469+
* connection to S2A. if {@param plaintextAddress} is not present, returns null.
470+
*
471+
* @param plaintextAddress the address to reach S2A which accepts plaintext connections
472+
* @return {@link ChannelCredentials} to use to create a plaintext connection between client and
473+
* S2A
474+
*/
475+
ChannelCredentials createPlaintextToS2AChannelCredentials(String plaintextAddress) {
476+
if (Strings.isNullOrEmpty(plaintextAddress)) {
477+
return null;
478+
}
479+
return S2AChannelCredentials.newBuilder(plaintextAddress, InsecureChannelCredentials.create())
480+
.build();
481+
}
482+
483+
/**
484+
* This method creates gRPC {@link ChannelCredentials} configured to use S2A to estbalish a mTLS
485+
* connection. First, the address of S2A is discovered by using the {@link S2A} utility to learn
486+
* the {@code mtlsAddress} to reach S2A and the {@code plaintextAddress} to reach S2A. Prefer to
487+
* use the {@code mtlsAddress} address to reach S2A if it is non-empty and the MTLS-MDS
488+
* credentials can successfully be discovered and used to create {@link TlsChannelCredentials}. If
489+
* there is any failure using mTLS-to-S2A, fallback to using a plaintext connection to S2A using
490+
* the {@code plaintextAddress}. If {@code plaintextAddress} is not available, this function
491+
* returns null; in this case S2A will not be used, and a TLS connection to the service will be
492+
* established.
493+
*
494+
* @return {@link ChannelCredentials} configured to use S2A to create mTLS connection to
495+
* mtlsEndpoint.
496+
*/
497+
ChannelCredentials createS2ASecuredChannelCredentials() {
498+
SecureSessionAgentConfig config = s2aConfigProvider.getConfig();
499+
String plaintextAddress = config.getPlaintextAddress();
500+
String mtlsAddress = config.getMtlsAddress();
501+
if (Strings.isNullOrEmpty(mtlsAddress)) {
502+
// Fallback to plaintext connection to S2A.
503+
LOG.log(
504+
Level.INFO,
505+
"Cannot establish an mTLS connection to S2A because autoconfig endpoint did not return a mtls address to reach S2A.");
506+
return createPlaintextToS2AChannelCredentials(plaintextAddress);
507+
}
508+
// Currently, MTLS to MDS is only available on GCE. See:
509+
// https://cloud.google.com/compute/docs/metadata/overview#https-mds
510+
// Try to load MTLS-MDS creds.
511+
File rootFile = new File(MTLS_MDS_ROOT_PATH);
512+
File certKeyFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY_PATH);
513+
if (rootFile.isFile() && certKeyFile.isFile()) {
514+
// Try to connect to S2A using mTLS.
515+
ChannelCredentials mtlsToS2AChannelCredentials = null;
516+
try {
517+
mtlsToS2AChannelCredentials =
518+
createMtlsToS2AChannelCredentials(rootFile, certKeyFile, certKeyFile);
519+
} catch (IOException ignore) {
520+
// Fallback to plaintext-to-S2A connection on error.
521+
LOG.log(
522+
Level.WARNING,
523+
"Cannot establish an mTLS connection to S2A due to error creating MTLS to MDS TlsChannelCredentials credentials, falling back to plaintext connection to S2A: "
524+
+ ignore.getMessage());
525+
return createPlaintextToS2AChannelCredentials(plaintextAddress);
526+
}
527+
return S2AChannelCredentials.newBuilder(mtlsAddress, mtlsToS2AChannelCredentials).build();
528+
} else {
529+
// Fallback to plaintext-to-S2A connection if MTLS-MDS creds do not exist.
530+
LOG.log(
531+
Level.INFO,
532+
"Cannot establish an mTLS connection to S2A because MTLS to MDS credentials do not exist on filesystem, falling back to plaintext connection to S2A");
533+
return createPlaintextToS2AChannelCredentials(plaintextAddress);
534+
}
535+
}
536+
413537
private ManagedChannel createSingleChannel() throws IOException {
414538
GrpcHeaderInterceptor headerInterceptor =
415539
new GrpcHeaderInterceptor(headersWithDuplicatesRemoved);
@@ -447,16 +571,31 @@ private ManagedChannel createSingleChannel() throws IOException {
447571
builder.keepAliveTime(DIRECT_PATH_KEEP_ALIVE_TIME_SECONDS, TimeUnit.SECONDS);
448572
builder.keepAliveTimeout(DIRECT_PATH_KEEP_ALIVE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
449573
} else {
574+
// Try and create credentials via DCA. See https://google.aip.dev/auth/4114.
450575
ChannelCredentials channelCredentials;
451576
try {
452577
channelCredentials = createMtlsChannelCredentials();
453578
} catch (GeneralSecurityException e) {
454579
throw new IOException(e);
455580
}
456581
if (channelCredentials != null) {
582+
// Create the channel using channel credentials created via DCA.
457583
builder = Grpc.newChannelBuilder(endpoint, channelCredentials);
458584
} else {
459-
builder = ManagedChannelBuilder.forAddress(serviceAddress, port);
585+
// Could not create channel credentials via DCA. In accordance with
586+
// https://google.aip.dev/auth/4115, if credentials not available through
587+
// DCA, try mTLS with credentials held by the S2A (Secure Session Agent).
588+
if (useS2A) {
589+
channelCredentials = createS2ASecuredChannelCredentials();
590+
}
591+
if (channelCredentials != null) {
592+
// Create the channel using S2A-secured channel credentials.
593+
// {@code endpoint} is set to mtlsEndpoint in {@link EndpointContext} when useS2A is true.
594+
builder = Grpc.newChannelBuilder(endpoint, channelCredentials);
595+
} else {
596+
// Use default if we cannot initialize channel credentials via DCA or S2A.
597+
builder = ManagedChannelBuilder.forAddress(serviceAddress, port);
598+
}
460599
}
461600
}
462601
// google-c2p resolver requires service config lookup
@@ -604,7 +743,9 @@ public static final class Builder {
604743
private Executor executor;
605744
private HeaderProvider headerProvider;
606745
private String endpoint;
746+
private boolean useS2A;
607747
private EnvironmentProvider envProvider;
748+
private SecureSessionAgent s2aConfigProvider = SecureSessionAgent.create();
608749
private MtlsProvider mtlsProvider = new MtlsProvider();
609750
@Nullable private GrpcInterceptorProvider interceptorProvider;
610751
@Nullable private Integer maxInboundMessageSize;
@@ -632,6 +773,7 @@ private Builder(InstantiatingGrpcChannelProvider provider) {
632773
this.executor = provider.executor;
633774
this.headerProvider = provider.headerProvider;
634775
this.endpoint = provider.endpoint;
776+
this.useS2A = provider.useS2A;
635777
this.envProvider = provider.envProvider;
636778
this.interceptorProvider = provider.interceptorProvider;
637779
this.maxInboundMessageSize = provider.maxInboundMessageSize;
@@ -648,6 +790,7 @@ private Builder(InstantiatingGrpcChannelProvider provider) {
648790
this.allowNonDefaultServiceAccount = provider.allowNonDefaultServiceAccount;
649791
this.directPathServiceConfig = provider.directPathServiceConfig;
650792
this.mtlsProvider = provider.mtlsProvider;
793+
this.s2aConfigProvider = provider.s2aConfigProvider;
651794
}
652795

653796
/**
@@ -700,12 +843,23 @@ public Builder setEndpoint(String endpoint) {
700843
return this;
701844
}
702845

846+
Builder setUseS2A(boolean useS2A) {
847+
this.useS2A = useS2A;
848+
return this;
849+
}
850+
703851
@VisibleForTesting
704852
Builder setMtlsProvider(MtlsProvider mtlsProvider) {
705853
this.mtlsProvider = mtlsProvider;
706854
return this;
707855
}
708856

857+
@VisibleForTesting
858+
Builder setS2AConfigProvider(SecureSessionAgent s2aConfigProvider) {
859+
this.s2aConfigProvider = s2aConfigProvider;
860+
return this;
861+
}
862+
709863
/**
710864
* Sets the GrpcInterceptorProvider for this TransportChannelProvider.
711865
*

‎gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/GrpcLongRunningTest.java

+2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ void setUp() throws IOException {
101101
TransportChannel transportChannel =
102102
GrpcTransportChannel.newBuilder().setManagedChannel(channel).build();
103103
when(operationsChannelProvider.getTransportChannel()).thenReturn(transportChannel);
104+
when(operationsChannelProvider.withUseS2A(Mockito.any(boolean.class)))
105+
.thenReturn(operationsChannelProvider);
104106

105107
clock = new FakeApiClock(0L);
106108
executor = RecordingScheduler.create(clock);

‎gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/InstantiatingGrpcChannelProviderTest.java

+118
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,16 @@
5151
import com.google.auth.http.AuthHttpConstants;
5252
import com.google.auth.oauth2.CloudShellCredentials;
5353
import com.google.auth.oauth2.ComputeEngineCredentials;
54+
import com.google.auth.oauth2.SecureSessionAgent;
55+
import com.google.auth.oauth2.SecureSessionAgentConfig;
5456
import com.google.common.collect.ImmutableList;
5557
import com.google.common.collect.ImmutableMap;
5658
import com.google.common.truth.Truth;
5759
import io.grpc.ManagedChannel;
5860
import io.grpc.ManagedChannelBuilder;
61+
import io.grpc.TlsChannelCredentials;
5962
import io.grpc.alts.ComputeEngineChannelBuilder;
63+
import java.io.File;
6064
import java.io.IOException;
6165
import java.security.GeneralSecurityException;
6266
import java.time.Duration;
@@ -980,6 +984,120 @@ private FixedHeaderProvider getHeaderProviderWithApiKeyHeader() {
980984
return FixedHeaderProvider.create(header);
981985
}
982986

987+
@Test
988+
void createPlaintextToS2AChannelCredentials_emptyPlaintextAddress_returnsNull() {
989+
InstantiatingGrpcChannelProvider provider =
990+
InstantiatingGrpcChannelProvider.newBuilder().build();
991+
assertThat(provider.createPlaintextToS2AChannelCredentials("")).isNull();
992+
}
993+
994+
@Test
995+
void createPlaintextToS2AChannelCredentials_success() {
996+
InstantiatingGrpcChannelProvider provider =
997+
InstantiatingGrpcChannelProvider.newBuilder().build();
998+
assertThat(provider.createPlaintextToS2AChannelCredentials("localhost:8080")).isNotNull();
999+
}
1000+
1001+
@Test
1002+
void createMtlsToS2AChannelCredentials_missingAllFiles_throws() throws IOException {
1003+
InstantiatingGrpcChannelProvider provider =
1004+
InstantiatingGrpcChannelProvider.newBuilder().build();
1005+
assertThat(provider.createMtlsToS2AChannelCredentials(null, null, null)).isNull();
1006+
}
1007+
1008+
@Test
1009+
void createMtlsToS2AChannelCredentials_missingRootFile_throws() throws IOException {
1010+
InstantiatingGrpcChannelProvider provider =
1011+
InstantiatingGrpcChannelProvider.newBuilder().build();
1012+
File privateKey = new File("src/test/resources/client_key.pem");
1013+
File certChain = new File("src/test/resources/client_cert.pem");
1014+
assertThat(provider.createMtlsToS2AChannelCredentials(null, privateKey, certChain)).isNull();
1015+
}
1016+
1017+
@Test
1018+
void createMtlsToS2AChannelCredentials_missingKeyFile_throws() throws IOException {
1019+
InstantiatingGrpcChannelProvider provider =
1020+
InstantiatingGrpcChannelProvider.newBuilder().build();
1021+
File trustBundle = new File("src/test/resources/root_cert.pem");
1022+
File certChain = new File("src/test/resources/client_cert.pem");
1023+
assertThat(provider.createMtlsToS2AChannelCredentials(trustBundle, null, certChain)).isNull();
1024+
}
1025+
1026+
@Test
1027+
void createMtlsToS2AChannelCredentials_missingCertChainFile_throws() throws IOException {
1028+
InstantiatingGrpcChannelProvider provider =
1029+
InstantiatingGrpcChannelProvider.newBuilder().build();
1030+
File trustBundle = new File("src/test/resources/root_cert.pem");
1031+
File privateKey = new File("src/test/resources/client_key.pem");
1032+
assertThat(provider.createMtlsToS2AChannelCredentials(trustBundle, privateKey, null)).isNull();
1033+
}
1034+
1035+
@Test
1036+
void createMtlsToS2AChannelCredentials_success() throws IOException {
1037+
InstantiatingGrpcChannelProvider provider =
1038+
InstantiatingGrpcChannelProvider.newBuilder().build();
1039+
File trustBundle = new File("src/test/resources/root_cert.pem");
1040+
File privateKey = new File("src/test/resources/client_key.pem");
1041+
File certChain = new File("src/test/resources/client_cert.pem");
1042+
assertEquals(
1043+
provider.createMtlsToS2AChannelCredentials(trustBundle, privateKey, certChain).getClass(),
1044+
TlsChannelCredentials.class);
1045+
}
1046+
1047+
@Test
1048+
void createS2ASecuredChannelCredentials_bothS2AAddressesNull_returnsNull() {
1049+
SecureSessionAgent s2aConfigProvider = Mockito.mock(SecureSessionAgent.class);
1050+
SecureSessionAgentConfig config = SecureSessionAgentConfig.createBuilder().build();
1051+
Mockito.when(s2aConfigProvider.getConfig()).thenReturn(config);
1052+
InstantiatingGrpcChannelProvider provider =
1053+
InstantiatingGrpcChannelProvider.newBuilder()
1054+
.setS2AConfigProvider(s2aConfigProvider)
1055+
.build();
1056+
assertThat(provider.createS2ASecuredChannelCredentials()).isNull();
1057+
}
1058+
1059+
@Test
1060+
void
1061+
createS2ASecuredChannelCredentials_mtlsS2AAddressNull_returnsPlaintextToS2AS2AChannelCredentials() {
1062+
SecureSessionAgent s2aConfigProvider = Mockito.mock(SecureSessionAgent.class);
1063+
SecureSessionAgentConfig config =
1064+
SecureSessionAgentConfig.createBuilder().setPlaintextAddress("localhost:8080").build();
1065+
Mockito.when(s2aConfigProvider.getConfig()).thenReturn(config);
1066+
FakeLogHandler logHandler = new FakeLogHandler();
1067+
InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler);
1068+
InstantiatingGrpcChannelProvider provider =
1069+
InstantiatingGrpcChannelProvider.newBuilder()
1070+
.setS2AConfigProvider(s2aConfigProvider)
1071+
.build();
1072+
assertThat(provider.createS2ASecuredChannelCredentials()).isNotNull();
1073+
assertThat(logHandler.getAllMessages())
1074+
.contains(
1075+
"Cannot establish an mTLS connection to S2A because autoconfig endpoint did not return a mtls address to reach S2A.");
1076+
InstantiatingGrpcChannelProvider.LOG.removeHandler(logHandler);
1077+
}
1078+
1079+
@Test
1080+
void createS2ASecuredChannelCredentials_returnsPlaintextToS2AS2AChannelCredentials() {
1081+
SecureSessionAgent s2aConfigProvider = Mockito.mock(SecureSessionAgent.class);
1082+
SecureSessionAgentConfig config =
1083+
SecureSessionAgentConfig.createBuilder()
1084+
.setMtlsAddress("localhost:8080")
1085+
.setPlaintextAddress("localhost:8080")
1086+
.build();
1087+
Mockito.when(s2aConfigProvider.getConfig()).thenReturn(config);
1088+
FakeLogHandler logHandler = new FakeLogHandler();
1089+
InstantiatingGrpcChannelProvider.LOG.addHandler(logHandler);
1090+
InstantiatingGrpcChannelProvider provider =
1091+
InstantiatingGrpcChannelProvider.newBuilder()
1092+
.setS2AConfigProvider(s2aConfigProvider)
1093+
.build();
1094+
assertThat(provider.createS2ASecuredChannelCredentials()).isNotNull();
1095+
assertThat(logHandler.getAllMessages())
1096+
.contains(
1097+
"Cannot establish an mTLS connection to S2A because MTLS to MDS credentials do not exist on filesystem, falling back to plaintext connection to S2A");
1098+
InstantiatingGrpcChannelProvider.LOG.removeHandler(logHandler);
1099+
}
1100+
9831101
private static class FakeLogHandler extends Handler {
9841102

9851103
List<LogRecord> records = new ArrayList<>();

‎gax-java/gax-grpc/src/test/java/com/google/api/gax/grpc/testing/LocalChannelProvider.java

+6
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ public TransportChannelProvider withEndpoint(String endpoint) {
106106
throw new UnsupportedOperationException("LocalChannelProvider doesn't need an endpoint");
107107
}
108108

109+
@Override
110+
public TransportChannelProvider withUseS2A(boolean useS2A) {
111+
// Overriden for technical reasons. This method is a no-op for LocalChannelProvider.
112+
return this;
113+
}
114+
109115
@Override
110116
@BetaApi("The surface for customizing pool size is not stable yet and may change in the future.")
111117
public boolean acceptsPoolSize() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Regenerate certificates and keys for testing mTLS-S2A
2+
Below are the commands which can be used to regenerate the certs used in tests. This is the same process
3+
used to generate test certs for S2A client in grpc-java: https://github.com/grpc/grpc-java/blob/master/s2a/src/test/resources/README.md
4+
5+
Create root CA
6+
7+
```
8+
openssl req -x509 -sha256 -days 7305 -newkey rsa:2048 -keyout root_key.pem -out
9+
root_cert.pem
10+
```
11+
12+
Generate private key
13+
14+
```
15+
openssl genrsa -out client_key.pem 2048
16+
```
17+
18+
Generate CSR (set Common Name to localhost, leave all
19+
other fields blank)
20+
21+
```
22+
openssl req -key client_key.pem -new -out client.csr -config config.cnf
23+
```
24+
25+
Sign CSR for client
26+
27+
```
28+
openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in client.csr -out client_cert.pem -days 7305
29+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDPTCCAiWgAwIBAgIUaarddwSWeE4jDC9kwxEr446ehqUwDQYJKoZIhvcNAQEL
3+
BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
4+
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X
5+
DTI0MTAwMTIxNTk1NFoXDTQ0MTAwMTIxNTk1NFowFDESMBAGA1UEAwwJbG9jYWxo
6+
b3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxlNsldt7yAU4KRuS
7+
2D2/FjNIE1US5olBm4HteTr++41WaELZJqNLRPPp052jEQU3aKSYNGZvUUO6buu7
8+
eFpz2SBNUVMyvmzzocjVAyyf4NQvDazYHWOb+/YCeUppTRWriz4V5sn47qJTQ8cd
9+
CGrTFeLHxUjx4nh/OiqVXP/KnF3EqPEuqph0ky7+GirnJgPRe+C5ERuGkJye8dmP
10+
yWGA2lSS6MeDe7JZTAMi08bAn7BuNpeBkOzz1msGGI9PnUanUs7GOPWTDdcQAVY8
11+
KMvHCuGaNMGpb4rOR2mm8LlbAbpTPz8Pkw4QtMCLkgsrz2CzXpVwnLsU7nDXJAIO
12+
B155lQIDAQABo0IwQDAdBgNVHQ4EFgQUSZEyIHLzkIw7AwkBaUjYfIrGVR4wHwYD
13+
VR0jBBgwFoAUcq3dtxAVA410YWyM0B4e+4umbiwwDQYJKoZIhvcNAQELBQADggEB
14+
AAz0bZ4ayrZLhA45xn0yvdpdqiCtiWikCRtxgE7VXHg/ziZJVMpBpAhbIGO5tIyd
15+
lttnRXHwz5DUwKiba4/bCEFe229BshQEql5qaqcbGbFfSly11WeqqnwR1N7c8Gpv
16+
pD9sVrx22seN0rTUk87MY/S7mzCxHqAx35zm/LTW3pWcgCTMKFHy4Gt4mpTnXkNA
17+
WkhP2OhW5RLiu6Whi0BEdb2TGG1+ctamgijKXb+gJeef5ehlHXG8eU862KF5UlEA
18+
NeQKBm/PpQxOMe0NdpatjN8QRoczku0Itiodng+OZ1o+2iSNG988uFRb3CUSnjtE
19+
R/HL6ULAFzo59EpIYxruU/w=
20+
-----END CERTIFICATE-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
-----BEGIN PRIVATE KEY-----
2+
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDGU2yV23vIBTgp
3+
G5LYPb8WM0gTVRLmiUGbge15Ov77jVZoQtkmo0tE8+nTnaMRBTdopJg0Zm9RQ7pu
4+
67t4WnPZIE1RUzK+bPOhyNUDLJ/g1C8NrNgdY5v79gJ5SmlNFauLPhXmyfjuolND
5+
xx0IatMV4sfFSPHieH86KpVc/8qcXcSo8S6qmHSTLv4aKucmA9F74LkRG4aQnJ7x
6+
2Y/JYYDaVJLox4N7sllMAyLTxsCfsG42l4GQ7PPWawYYj0+dRqdSzsY49ZMN1xAB
7+
Vjwoy8cK4Zo0walvis5HaabwuVsBulM/Pw+TDhC0wIuSCyvPYLNelXCcuxTucNck
8+
Ag4HXnmVAgMBAAECggEAKuW9jXaBgiS63o1jyFkmvWcPNntG0M2sfrXuRzQfFgse
9+
vwOCk8xrSflWQNsOe+58ayp6746ekl3LdBWSIbiy6SqG/sm3pp/LXNmjVYHv/QH4
10+
QYV643R5t1ihdVnGiBFhXwdpVleme/tpdjYZzgnJKak5W69o/nrgzhSK5ShAy2xM
11+
j0XXbgdqG+4JxPb5BZmjHHfXAXUfgSORMdfArkbgFBRc9wL/6JVTXjeAMy5WX9qe
12+
5UQsSOYkwc9P2snifC/jdIhjHQOkkx59O0FgukJEFZPoagVG1duWQbnNDr7QVHCJ
13+
jV6dg9tIT4SXD3uPSPbgNGlRUseIakCzrhHARJuA2wKBgQD/h8zoh0KaqKyViCYw
14+
XKOFpm1pAFnp2GiDOblxNubNFAXEWnC+FlkvO/z1s0zVuYELUqfxcYMSXJFEVelK
15+
rfjZtoC5oxqWGqLo9iCj7pa8t+ipulYcLt2SWc7eZPD4T4lzeEf1Qz77aKcz34sa
16+
dv9lzQkDvhR/Mv1VeEGFHiq2VwKBgQDGsLcTGH5Yxs//LRSY8TigBkQEDrH5NvXu
17+
2jtAzZhy1Yhsoa5eiZkhnnzM6+n05ovfZLcy6s7dnwP1Y+C79vs+DKMBsodtDG5z
18+
YpsB0VrXYa6P6pCqkcz0Bz9xdo5sOhAK3AKnX6jd29XBDdeYsw/lxHLG24wProTD
19+
cCYFqtaj8wKBgQCaqKT68DL9zK14a8lBaDCIyexaqx3AjXzkP+Hfhi03XrEG4P5v
20+
7rLYBeTbCUSt7vMN2V9QoTWFvYUm6SCkVJvTmcRblz6WL1T+z0l+LwAJBP7LC77m
21+
m+77j2PH8yxt/iXhP6G97o+GNxdMLDbTM8bs5KZaH4fkXQY73uc5HMMZTQKBgEZS
22+
7blYhf+t/ph2wD+RwVUCYrh86wkmJs2veCFro3WhlnO8lhbn5Mc9bTaqmVgQ8ZjT
23+
8POYoDdYvPHxs+1TcYF4v4kuQziZmc5FLE/sZZauADb38tQsXrpQhmgGakpsEpmF
24+
XXsYJJDB6lo2KATn+8x7R5SSyHQUdPEnlI2U9ft5AoGBAJw0NJiM1EzRS8xq0DmO
25+
AvQaPjo01o2hH6wghws8gDQwrj0eHraHgVi7zo0VkaHJbO7ahKPudset3N7owJhA
26+
CUAPPRtv5wn0amAyNz77f1dz4Gys3AkcchflqhbEaQpzKYx4kX0adclur4WJ/DVm
27+
P7DI977SHCVB4FVMbXMEkBjN
28+
-----END PRIVATE KEY-----
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-----BEGIN CERTIFICATE-----
2+
MIIDkzCCAnugAwIBAgIUWemeXZdfqcqkP8/Eyj74oTJtoNQwDQYJKoZIhvcNAQEL
3+
BQAwWTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
4+
GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MB4X
5+
DTI0MTAwMTIxNTkxMVoXDTQ0MTAwMTIxNTkxMVowWTELMAkGA1UEBhMCQVUxEzAR
6+
BgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5
7+
IEx0ZDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
8+
MIIBCgKCAQEAt3A04hy5lljv86Nu0LLQZ2hA+fcImHjt1p1Mxgcta/5oxfVLcerE
9+
ZH+DAQLDtWzp9Up/vI57MM419GIL8Iszk7hnZRS/HWJ+2jewZJtz4i/g15dLr6+1
10+
uabMdPOWos60BwcLMxKEe6lJO1mV4z9d4NH4mAuMIHyM+ty0Klp9MfeDJtYEh0+z
11+
AxJUHCixDTsnKJro7My7A3ZT7bvaMfXxS7XN6qlRgBfiCmXo/GKTFfmfBW/EZGkG
12+
XOCxE2D79wYNhC41Q/ix0kwjEeOj2vgGFoiyblSdHdzvRXzsoQTEiZSM8lJDR2IT
13+
ZbpgbBlknMU6efNWlS8P5damB9ZWXg3x4wIDAQABo1MwUTAdBgNVHQ4EFgQUcq3d
14+
txAVA410YWyM0B4e+4umbiwwHwYDVR0jBBgwFoAUcq3dtxAVA410YWyM0B4e+4um
15+
biwwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEApZvaI9y7vjX/
16+
RRdvwf2Db9KlTE9nuVQ3AsrmG9Ml0p2X6U5aTetxdYBo2PuaaYHheF03JOH8zjpL
17+
UfFzvbi52DPbfFAaDw/6NIAenXlg492leNvUFNjGGRyJO9R5/aDfv40/fT3Em5G5
18+
DnR8SeGQ9tI1t6xBBT+d+/MilSiEKVu8IIF/p0SwvEyR4pKo6wFVZR0ZiIj2v/FZ
19+
P5Qk0Xhb+slpmaR3Wtx/mPl9Wb3kpPD4CAwhWDqFkKJql9/n9FvMjdwlCQKQGB26
20+
ZDXY3C0UTdktK5biNWRgAUVJEWBX6Q2amrxQHIn2d9RJ8uxCME/KBAntK+VxZE78
21+
w0JOvQ4Dpw==
22+
-----END CERTIFICATE-----

‎gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/InstantiatingHttpJsonChannelProvider.java

+5
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ public TransportChannelProvider withEndpoint(String endpoint) {
124124
return toBuilder().setEndpoint(endpoint).build();
125125
}
126126

127+
@Override
128+
public TransportChannelProvider withUseS2A(boolean useS2A) {
129+
return this;
130+
}
131+
127132
/** @deprecated REST transport channel doesn't support channel pooling */
128133
@Deprecated
129134
@Override

‎gax-java/gax/clirr-ignored-differences.xml

+12
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,16 @@
106106
<className>com/google/api/gax/batching/Batcher</className>
107107
<method>*</method>
108108
</difference>
109+
<!-- Ignore abstract method addition to an EndpointContext-->
110+
<difference>
111+
<differenceType>7013</differenceType>
112+
<className>com/google/api/gax/rpc/EndpointContext</className>
113+
<method>* useS2A()</method>
114+
</difference>
115+
<!-- Ignore method addition to an TransportChannelProvider interface-->
116+
<difference>
117+
<differenceType>7012</differenceType>
118+
<className>com/google/api/gax/rpc/TransportChannelProvider</className>
119+
<method>* withUseS2A(*)</method>
120+
</difference>
109121
</differences>

‎gax-java/gax/src/main/java/com/google/api/gax/rpc/ClientContext.java

+1
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ public static ClientContext create(StubSettings settings) throws IOException {
222222
if (transportChannelProvider.needsEndpoint()) {
223223
transportChannelProvider = transportChannelProvider.withEndpoint(endpoint);
224224
}
225+
transportChannelProvider = transportChannelProvider.withUseS2A(endpointContext.useS2A());
225226
TransportChannel transportChannel = transportChannelProvider.getTransportChannel();
226227

227228
ApiCallContext defaultCallContext =

‎gax-java/gax/src/main/java/com/google/api/gax/rpc/EndpointContext.java

+50-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
package com.google.api.gax.rpc;
3131

3232
import com.google.api.core.InternalApi;
33+
import com.google.api.gax.rpc.internal.EnvironmentProvider;
3334
import com.google.api.gax.rpc.mtls.MtlsProvider;
3435
import com.google.auth.Credentials;
3536
import com.google.auth.oauth2.ComputeEngineCredentials;
@@ -65,6 +66,9 @@ public abstract class EndpointContext {
6566
"The configured universe domain (%s) does not match the universe domain found in the credentials (%s). If you haven't configured the universe domain explicitly, `googleapis.com` is the default.";
6667
public static final String UNABLE_TO_RETRIEVE_CREDENTIALS_ERROR_MESSAGE =
6768
"Unable to retrieve the Universe Domain from the Credentials.";
69+
// This environment variable is a temporary measure. It will be removed when the feature is
70+
// non-experimental.
71+
static final String S2A_ENV_ENABLE_USE_S2A = "EXPERIMENTAL_GOOGLE_API_USE_S2A";
6872

6973
public static EndpointContext getDefaultInstance() {
7074
return INSTANCE;
@@ -100,6 +104,11 @@ public static EndpointContext getDefaultInstance() {
100104
@Nullable
101105
public abstract String transportChannelProviderEndpoint();
102106

107+
abstract boolean useS2A();
108+
109+
@Nullable
110+
abstract EnvironmentProvider envProvider();
111+
103112
@Nullable
104113
public abstract String mtlsEndpoint();
105114

@@ -119,7 +128,8 @@ public static EndpointContext getDefaultInstance() {
119128
public static Builder newBuilder() {
120129
return new AutoValue_EndpointContext.Builder()
121130
.setSwitchToMtlsEndpointAllowed(false)
122-
.setUsingGDCH(false);
131+
.setUsingGDCH(false)
132+
.setEnvProvider(System::getenv);
123133
}
124134

125135
/** Configure the existing EndpointContext to be using GDC-H */
@@ -208,6 +218,10 @@ public abstract static class Builder {
208218

209219
public abstract Builder setResolvedUniverseDomain(String resolvedUniverseDomain);
210220

221+
abstract Builder setUseS2A(boolean useS2A);
222+
223+
abstract Builder setEnvProvider(EnvironmentProvider envProvider);
224+
211225
abstract String serviceName();
212226

213227
abstract String universeDomain();
@@ -216,6 +230,10 @@ public abstract static class Builder {
216230

217231
abstract String transportChannelProviderEndpoint();
218232

233+
abstract boolean useS2A();
234+
235+
abstract EnvironmentProvider envProvider();
236+
219237
abstract String mtlsEndpoint();
220238

221239
abstract boolean switchToMtlsEndpointAllowed();
@@ -254,6 +272,10 @@ private String determineUniverseDomain() {
254272

255273
/** Determines the fully resolved endpoint and universe domain values */
256274
private String determineEndpoint() throws IOException {
275+
if (shouldUseS2A()) {
276+
return mtlsEndpoint();
277+
}
278+
257279
MtlsProvider mtlsProvider = mtlsProvider() == null ? new MtlsProvider() : mtlsProvider();
258280
// TransportChannelProvider's endpoint will override the ClientSettings' endpoint
259281
String customEndpoint =
@@ -288,6 +310,32 @@ private String determineEndpoint() throws IOException {
288310
return endpoint;
289311
}
290312

313+
/** Determine if S2A can be used */
314+
@VisibleForTesting
315+
boolean shouldUseS2A() {
316+
// If EXPERIMENTAL_GOOGLE_API_USE_S2A is not set to true, skip S2A.
317+
String s2AEnv;
318+
s2AEnv = envProvider().getenv(S2A_ENV_ENABLE_USE_S2A);
319+
boolean s2AEnabled = Boolean.parseBoolean(s2AEnv);
320+
if (!s2AEnabled) {
321+
return false;
322+
}
323+
324+
// Skip S2A when using GDC-H
325+
if (usingGDCH()) {
326+
return false;
327+
}
328+
329+
// If a custom endpoint is being used, skip S2A.
330+
if (!Strings.isNullOrEmpty(clientSettingsEndpoint())
331+
|| !Strings.isNullOrEmpty(transportChannelProviderEndpoint())) {
332+
return false;
333+
}
334+
335+
// mTLS via S2A is not supported in any universe other than googleapis.com.
336+
return mtlsEndpoint().contains(Credentials.GOOGLE_DEFAULT_UNIVERSE);
337+
}
338+
291339
// Default to port 443 for HTTPS. Using HTTP requires explicitly setting the endpoint
292340
private String buildEndpointTemplate(String serviceName, String resolvedUniverseDomain) {
293341
return serviceName + "." + resolvedUniverseDomain + ":443";
@@ -321,6 +369,7 @@ public EndpointContext build() throws IOException {
321369
// The Universe Domain is used to resolve the Endpoint. It should be resolved first
322370
setResolvedUniverseDomain(determineUniverseDomain());
323371
setResolvedEndpoint(determineEndpoint());
372+
setUseS2A(shouldUseS2A());
324373
return autoBuild();
325374
}
326375
}

‎gax-java/gax/src/main/java/com/google/api/gax/rpc/FixedTransportChannelProvider.java

+6
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ public TransportChannelProvider withEndpoint(String endpoint) {
8989
"FixedTransportChannelProvider doesn't need an endpoint");
9090
}
9191

92+
@Override
93+
public TransportChannelProvider withUseS2A(boolean useS2A) throws UnsupportedOperationException {
94+
// Overriden for technical reasons. This method is a no-op for FixedTransportChannelProvider.
95+
return this;
96+
}
97+
9298
/** @deprecated FixedTransportChannelProvider doesn't support ChannelPool configuration */
9399
@Deprecated
94100
@Override

‎gax-java/gax/src/main/java/com/google/api/gax/rpc/TransportChannelProvider.java

+5
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ public interface TransportChannelProvider {
9797
*/
9898
TransportChannelProvider withEndpoint(String endpoint);
9999

100+
/** Sets whether to use S2A when constructing a new {@link TransportChannel}. */
101+
default TransportChannelProvider withUseS2A(boolean useS2A) {
102+
throw new UnsupportedOperationException("S2A is not supported");
103+
}
104+
100105
/**
101106
* Reports whether this provider allows pool size customization.
102107
*

‎gax-java/gax/src/test/java/com/google/api/gax/rpc/ClientContextTest.java

+11
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,17 @@ public TransportChannelProvider withEndpoint(String endpoint) {
195195
endpoint);
196196
}
197197

198+
@Override
199+
public TransportChannelProvider withUseS2A(boolean useS2A) {
200+
return new FakeTransportProvider(
201+
this.transport,
202+
this.executor,
203+
this.shouldAutoClose,
204+
this.headers,
205+
this.credentials,
206+
this.endpoint);
207+
}
208+
198209
@Override
199210
public boolean acceptsPoolSize() {
200211
return false;

‎gax-java/gax/src/test/java/com/google/api/gax/rpc/EndpointContextTest.java

+94
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import static org.junit.jupiter.api.Assertions.assertThrows;
3434

3535
import com.google.api.gax.core.NoCredentialsProvider;
36+
import com.google.api.gax.rpc.internal.EnvironmentProvider;
3637
import com.google.api.gax.rpc.mtls.MtlsProvider;
3738
import com.google.api.gax.rpc.testing.FakeMtlsProvider;
3839
import com.google.auth.Credentials;
@@ -454,4 +455,97 @@ void hasValidUniverseDomain_computeEngineCredentials_noValidationOnUniverseDomai
454455
.build();
455456
assertDoesNotThrow(() -> endpointContext.validateUniverseDomain(credentials, statusCode));
456457
}
458+
459+
@Test
460+
void shouldUseS2A_envVarNotSet_returnsFalse() throws IOException {
461+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
462+
Mockito.when(envProvider.getenv(EndpointContext.S2A_ENV_ENABLE_USE_S2A)).thenReturn("false");
463+
defaultEndpointContextBuilder =
464+
defaultEndpointContextBuilder
465+
.setEnvProvider(envProvider)
466+
.setClientSettingsEndpoint("")
467+
.setTransportChannelProviderEndpoint("")
468+
.setUsingGDCH(false);
469+
Truth.assertThat(defaultEndpointContextBuilder.shouldUseS2A()).isFalse();
470+
}
471+
472+
@Test
473+
void shouldUseS2A_UsingGDCH_returnsFalse() throws IOException {
474+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
475+
Mockito.when(envProvider.getenv(EndpointContext.S2A_ENV_ENABLE_USE_S2A)).thenReturn("true");
476+
defaultEndpointContextBuilder =
477+
defaultEndpointContextBuilder
478+
.setEnvProvider(envProvider)
479+
.setClientSettingsEndpoint("")
480+
.setTransportChannelProviderEndpoint("")
481+
.setUsingGDCH(true);
482+
Truth.assertThat(defaultEndpointContextBuilder.shouldUseS2A()).isFalse();
483+
}
484+
485+
@Test
486+
void shouldUseS2A_customEndpointSetViaClientSettings_returnsFalse() throws IOException {
487+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
488+
Mockito.when(envProvider.getenv(EndpointContext.S2A_ENV_ENABLE_USE_S2A)).thenReturn("true");
489+
defaultEndpointContextBuilder =
490+
defaultEndpointContextBuilder
491+
.setEnvProvider(envProvider)
492+
.setClientSettingsEndpoint("test.endpoint.com:443")
493+
.setTransportChannelProviderEndpoint("")
494+
.setUsingGDCH(false);
495+
Truth.assertThat(defaultEndpointContextBuilder.shouldUseS2A()).isFalse();
496+
}
497+
498+
@Test
499+
void shouldUseS2A_customEndpointSetViaTransportChannelProvider_returnsFalse() throws IOException {
500+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
501+
Mockito.when(envProvider.getenv(EndpointContext.S2A_ENV_ENABLE_USE_S2A)).thenReturn("true");
502+
defaultEndpointContextBuilder =
503+
defaultEndpointContextBuilder
504+
.setEnvProvider(envProvider)
505+
.setClientSettingsEndpoint("")
506+
.setTransportChannelProviderEndpoint("test.endpoint.com:443")
507+
.setUsingGDCH(false);
508+
Truth.assertThat(defaultEndpointContextBuilder.shouldUseS2A()).isFalse();
509+
}
510+
511+
@Test
512+
void shouldUseS2A_mtlsEndpointEmpty_returnsFalse() throws IOException {
513+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
514+
Mockito.when(envProvider.getenv(EndpointContext.S2A_ENV_ENABLE_USE_S2A)).thenReturn("true");
515+
defaultEndpointContextBuilder =
516+
defaultEndpointContextBuilder
517+
.setEnvProvider(envProvider)
518+
.setClientSettingsEndpoint("")
519+
.setTransportChannelProviderEndpoint("")
520+
.setMtlsEndpoint("")
521+
.setUsingGDCH(false);
522+
Truth.assertThat(defaultEndpointContextBuilder.shouldUseS2A()).isFalse();
523+
}
524+
525+
@Test
526+
void shouldUseS2A_mtlsEndpointNotGoogleDefaultUniverse_returnsFalse() throws IOException {
527+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
528+
Mockito.when(envProvider.getenv(EndpointContext.S2A_ENV_ENABLE_USE_S2A)).thenReturn("true");
529+
defaultEndpointContextBuilder =
530+
defaultEndpointContextBuilder
531+
.setEnvProvider(envProvider)
532+
.setClientSettingsEndpoint("")
533+
.setTransportChannelProviderEndpoint("")
534+
.setMtlsEndpoint("test.mtls.abcd.com:443")
535+
.setUsingGDCH(false);
536+
Truth.assertThat(defaultEndpointContextBuilder.shouldUseS2A()).isFalse();
537+
}
538+
539+
@Test
540+
void shouldUseS2A_success() throws IOException {
541+
EnvironmentProvider envProvider = Mockito.mock(EnvironmentProvider.class);
542+
Mockito.when(envProvider.getenv(EndpointContext.S2A_ENV_ENABLE_USE_S2A)).thenReturn("true");
543+
defaultEndpointContextBuilder =
544+
defaultEndpointContextBuilder
545+
.setEnvProvider(envProvider)
546+
.setClientSettingsEndpoint("")
547+
.setTransportChannelProviderEndpoint("")
548+
.setUsingGDCH(false);
549+
Truth.assertThat(defaultEndpointContextBuilder.shouldUseS2A()).isTrue();
550+
}
457551
}

0 commit comments

Comments
 (0)
Please sign in to comment.