Skip to content

Commit 3758b43

Browse files
authored
fix: Create a single S2AChannelCredentials per application (#3989)
1 parent 178182c commit 3758b43

File tree

2 files changed

+104
-35
lines changed

2 files changed

+104
-35
lines changed

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

Lines changed: 80 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,34 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
162162
@Nullable
163163
private final ApiFunction<ManagedChannelBuilder, ManagedChannelBuilder> channelConfigurator;
164164

165+
// This is initialized once for the lifetime of the application. This enables re-using
166+
// channels to S2A.
167+
private static volatile ChannelCredentials s2aChannelCredentials;
168+
169+
/**
170+
* Resets the s2aChannelCredentials of the {@link InstantiatingGrpcChannelProvider} class for
171+
* testing purposes.
172+
*
173+
* <p>This should only be called from tests.
174+
*/
175+
@VisibleForTesting
176+
static void resetS2AChannelCredentials() {
177+
synchronized (InstantiatingGrpcChannelProvider.class) {
178+
s2aChannelCredentials = null;
179+
}
180+
}
181+
182+
/**
183+
* Returns the s2aChannelCredentials of the {@link InstantiatingGrpcChannelProvider} class for
184+
* testing purposes.
185+
*
186+
* <p>This should only be called from tests.
187+
*/
188+
@VisibleForTesting
189+
static ChannelCredentials getS2AChannelCredentials() {
190+
return s2aChannelCredentials;
191+
}
192+
165193
/*
166194
* Experimental feature
167195
*
@@ -609,43 +637,60 @@ ChannelCredentials createPlaintextToS2AChannelCredentials(String plaintextAddres
609637
* @return {@link ChannelCredentials} configured to use S2A to create mTLS connection.
610638
*/
611639
ChannelCredentials createS2ASecuredChannelCredentials() {
612-
SecureSessionAgentConfig config = s2aConfigProvider.getConfig();
613-
String plaintextAddress = config.getPlaintextAddress();
614-
String mtlsAddress = config.getMtlsAddress();
615-
if (Strings.isNullOrEmpty(mtlsAddress)) {
616-
// Fallback to plaintext connection to S2A.
617-
LOG.log(
618-
Level.INFO,
619-
"Cannot establish an mTLS connection to S2A because autoconfig endpoint did not return a mtls address to reach S2A.");
620-
return createPlaintextToS2AChannelCredentials(plaintextAddress);
621-
}
622-
// Currently, MTLS to MDS is only available on GCE. See:
623-
// https://cloud.google.com/compute/docs/metadata/overview#https-mds
624-
// Try to load MTLS-MDS creds.
625-
File rootFile = new File(MTLS_MDS_ROOT_PATH);
626-
File certKeyFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY_PATH);
627-
if (rootFile.isFile() && certKeyFile.isFile()) {
628-
// Try to connect to S2A using mTLS.
629-
ChannelCredentials mtlsToS2AChannelCredentials = null;
630-
try {
631-
mtlsToS2AChannelCredentials =
632-
createMtlsToS2AChannelCredentials(rootFile, certKeyFile, certKeyFile);
633-
} catch (IOException ignore) {
634-
// Fallback to plaintext-to-S2A connection on error.
635-
LOG.log(
636-
Level.WARNING,
637-
"Cannot establish an mTLS connection to S2A due to error creating MTLS to MDS TlsChannelCredentials credentials, falling back to plaintext connection to S2A: "
638-
+ ignore.getMessage());
639-
return createPlaintextToS2AChannelCredentials(plaintextAddress);
640+
if (s2aChannelCredentials == null) {
641+
// s2aChannelCredentials is initialized once and shared by all instances of the class.
642+
// To prevent a race on initialization, the object initialization is synchronized on the class
643+
// object.
644+
synchronized (InstantiatingGrpcChannelProvider.class) {
645+
if (s2aChannelCredentials != null) {
646+
return s2aChannelCredentials;
647+
}
648+
SecureSessionAgentConfig config = s2aConfigProvider.getConfig();
649+
String plaintextAddress = config.getPlaintextAddress();
650+
String mtlsAddress = config.getMtlsAddress();
651+
if (Strings.isNullOrEmpty(mtlsAddress)) {
652+
// Fallback to plaintext connection to S2A.
653+
LOG.log(
654+
Level.INFO,
655+
"Cannot establish an mTLS connection to S2A because autoconfig endpoint did not return a mtls address to reach S2A.");
656+
s2aChannelCredentials = createPlaintextToS2AChannelCredentials(plaintextAddress);
657+
return s2aChannelCredentials;
658+
}
659+
// Currently, MTLS to MDS is only available on GCE. See:
660+
// https://cloud.google.com/compute/docs/metadata/overview#https-mds
661+
// Try to load MTLS-MDS creds.
662+
File rootFile = new File(MTLS_MDS_ROOT_PATH);
663+
File certKeyFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY_PATH);
664+
if (rootFile.isFile() && certKeyFile.isFile()) {
665+
// Try to connect to S2A using mTLS.
666+
ChannelCredentials mtlsToS2AChannelCredentials = null;
667+
try {
668+
mtlsToS2AChannelCredentials =
669+
createMtlsToS2AChannelCredentials(rootFile, certKeyFile, certKeyFile);
670+
} catch (IOException ignore) {
671+
// Fallback to plaintext-to-S2A connection on error.
672+
LOG.log(
673+
Level.WARNING,
674+
"Cannot establish an mTLS connection to S2A due to error creating MTLS to MDS TlsChannelCredentials credentials, falling back to plaintext connection to S2A: "
675+
+ ignore.getMessage());
676+
s2aChannelCredentials = createPlaintextToS2AChannelCredentials(plaintextAddress);
677+
return s2aChannelCredentials;
678+
}
679+
s2aChannelCredentials =
680+
buildS2AChannelCredentials(mtlsAddress, mtlsToS2AChannelCredentials);
681+
return s2aChannelCredentials;
682+
} else {
683+
// Fallback to plaintext-to-S2A connection if MTLS-MDS creds do not exist.
684+
LOG.log(
685+
Level.INFO,
686+
"Cannot establish an mTLS connection to S2A because MTLS to MDS credentials do not"
687+
+ " exist on filesystem, falling back to plaintext connection to S2A");
688+
s2aChannelCredentials = createPlaintextToS2AChannelCredentials(plaintextAddress);
689+
return s2aChannelCredentials;
690+
}
640691
}
641-
return buildS2AChannelCredentials(mtlsAddress, mtlsToS2AChannelCredentials);
642-
} else {
643-
// Fallback to plaintext-to-S2A connection if MTLS-MDS creds do not exist.
644-
LOG.log(
645-
Level.INFO,
646-
"Cannot establish an mTLS connection to S2A because MTLS to MDS credentials do not exist on filesystem, falling back to plaintext connection to S2A");
647-
return createPlaintextToS2AChannelCredentials(plaintextAddress);
648692
}
693+
return s2aChannelCredentials;
649694
}
650695

651696
private ManagedChannel createSingleChannel() throws IOException {

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,7 @@ void createMtlsToS2AChannelCredentials_success() throws IOException {
11471147

11481148
@Test
11491149
void createS2ASecuredChannelCredentials_bothS2AAddressesNull_returnsNull() {
1150+
InstantiatingGrpcChannelProvider.resetS2AChannelCredentials();
11501151
SecureSessionAgent s2aConfigProvider = Mockito.mock(SecureSessionAgent.class);
11511152
SecureSessionAgentConfig config = SecureSessionAgentConfig.createBuilder().build();
11521153
Mockito.when(s2aConfigProvider.getConfig()).thenReturn(config);
@@ -1160,6 +1161,7 @@ void createS2ASecuredChannelCredentials_bothS2AAddressesNull_returnsNull() {
11601161
@Test
11611162
void
11621163
createS2ASecuredChannelCredentials_mtlsS2AAddressNull_returnsPlaintextToS2AS2AChannelCredentials() {
1164+
InstantiatingGrpcChannelProvider.resetS2AChannelCredentials();
11631165
SecureSessionAgent s2aConfigProvider = Mockito.mock(SecureSessionAgent.class);
11641166
SecureSessionAgentConfig config =
11651167
SecureSessionAgentConfig.createBuilder().setPlaintextAddress("localhost:8080").build();
@@ -1177,8 +1179,30 @@ void createS2ASecuredChannelCredentials_bothS2AAddressesNull_returnsNull() {
11771179
InstantiatingGrpcChannelProvider.LOG.removeHandler(logHandler);
11781180
}
11791181

1182+
@Test
1183+
void
1184+
createTwoS2ASecuredChannelCredentials_mtlsS2AAddressNull_returnsSamePlaintextToS2AS2AChannelCredentials() {
1185+
InstantiatingGrpcChannelProvider.resetS2AChannelCredentials();
1186+
SecureSessionAgent s2aConfigProvider = Mockito.mock(SecureSessionAgent.class);
1187+
SecureSessionAgentConfig config =
1188+
SecureSessionAgentConfig.createBuilder().setPlaintextAddress("localhost:8080").build();
1189+
Mockito.when(s2aConfigProvider.getConfig()).thenReturn(config);
1190+
InstantiatingGrpcChannelProvider provider =
1191+
InstantiatingGrpcChannelProvider.newBuilder()
1192+
.setS2AConfigProvider(s2aConfigProvider)
1193+
.build();
1194+
assertThat(provider.createS2ASecuredChannelCredentials()).isNotNull();
1195+
InstantiatingGrpcChannelProvider provider2 =
1196+
InstantiatingGrpcChannelProvider.newBuilder()
1197+
.setS2AConfigProvider(s2aConfigProvider)
1198+
.build();
1199+
assertThat(provider2.createS2ASecuredChannelCredentials()).isNotNull();
1200+
assertEquals(provider.getS2AChannelCredentials(), provider2.getS2AChannelCredentials());
1201+
}
1202+
11801203
@Test
11811204
void createS2ASecuredChannelCredentials_returnsPlaintextToS2AS2AChannelCredentials() {
1205+
InstantiatingGrpcChannelProvider.resetS2AChannelCredentials();
11821206
SecureSessionAgent s2aConfigProvider = Mockito.mock(SecureSessionAgent.class);
11831207
SecureSessionAgentConfig config =
11841208
SecureSessionAgentConfig.createBuilder()

0 commit comments

Comments
 (0)