Skip to content

Commit d6ba979

Browse files
committed
implementation.
1 parent b456935 commit d6ba979

File tree

11 files changed

+265
-31
lines changed

11 files changed

+265
-31
lines changed

gapic-generator-java/pom.xml

+2
Original file line numberDiff line numberDiff line change
@@ -397,10 +397,12 @@
397397
<dependency>
398398
<groupId>com.google.api</groupId>
399399
<artifactId>gax-grpc</artifactId>
400+
<version>2.57.1-SNAPSHOT</version>
400401
</dependency>
401402
<dependency>
402403
<groupId>com.google.api</groupId>
403404
<artifactId>gax-grpc</artifactId>
405+
<version>2.57.1-SNAPSHOT</version>
404406
<!-- import the test code, https://maven.apache.org/plugins/maven-jar-plugin/examples/create-test-jar.html -->
405407
<type>test-jar</type>
406408
<classifier>testlib</classifier>

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:s2a",
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

+118-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
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.S2A;
4950
import com.google.common.annotations.VisibleForTesting;
5051
import com.google.common.base.Preconditions;
5152
import com.google.common.collect.ImmutableList;
@@ -54,11 +55,13 @@
5455
import io.grpc.CallCredentials;
5556
import io.grpc.ChannelCredentials;
5657
import io.grpc.Grpc;
58+
import io.grpc.InsecureChannelCredentials;
5759
import io.grpc.ManagedChannel;
5860
import io.grpc.ManagedChannelBuilder;
5961
import io.grpc.TlsChannelCredentials;
6062
import io.grpc.alts.GoogleDefaultChannelCredentials;
6163
import io.grpc.auth.MoreCallCredentials;
64+
import io.grpc.s2a.S2AChannelCredentials;
6265
import java.io.File;
6366
import java.io.IOException;
6467
import java.nio.charset.StandardCharsets;
@@ -99,6 +102,12 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
99102
@VisibleForTesting
100103
static final String DIRECT_PATH_ENV_ENABLE_XDS = "GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS";
101104

105+
private static final String S2A_ENV_ENABLE_USE_S2A = "EXPERIMENTAL_GOOGLE_API_USE_S2A";
106+
private static final String MTLS_MDS_ROOT = "/run/google-mds-mtls/root.crt";
107+
// The mTLS MDS credentials are formatted as the concatenation of a PEM-encoded certificate chain
108+
// followed by a PEM-encoded private key.
109+
private static final String MTLS_MDS_CERT_CHAIN_AND_KEY = "/run/google-mds-mtls/client.key";
110+
102111
static final long DIRECT_PATH_KEEP_ALIVE_TIME_SECONDS = 3600;
103112
static final long DIRECT_PATH_KEEP_ALIVE_TIMEOUT_SECONDS = 20;
104113
static final String GCE_PRODUCTION_NAME_PRIOR_2016 = "Google";
@@ -108,6 +117,7 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP
108117
private final Executor executor;
109118
private final HeaderProvider headerProvider;
110119
private final String endpoint;
120+
private final String mtlsEndpoint;
111121
// TODO: remove. envProvider currently provides DirectPath environment variable, and is only used
112122
// during initial rollout for DirectPath. This provider will be removed once the DirectPath
113123
// environment is not used.
@@ -136,6 +146,7 @@ private InstantiatingGrpcChannelProvider(Builder builder) {
136146
this.executor = builder.executor;
137147
this.headerProvider = builder.headerProvider;
138148
this.endpoint = builder.endpoint;
149+
this.mtlsEndpoint = builder.mtlsEndpoint;
139150
this.mtlsProvider = builder.mtlsProvider;
140151
this.envProvider = builder.envProvider;
141152
this.interceptorProvider = builder.interceptorProvider;
@@ -211,6 +222,10 @@ public boolean needsEndpoint() {
211222
return endpoint == null;
212223
}
213224

225+
public boolean needsMtlsEndpoint() {
226+
return mtlsEndpoint == null;
227+
}
228+
214229
/**
215230
* Specify the endpoint the channel should connect to.
216231
*
@@ -225,6 +240,17 @@ public TransportChannelProvider withEndpoint(String endpoint) {
225240
return toBuilder().setEndpoint(endpoint).build();
226241
}
227242

243+
/**
244+
* Specify the MTLS endpoint the channel should connect to.
245+
*
246+
* @param mtlsEndpoint
247+
* @return A new {@link InstantiatingGrpcChannelProvider} with the specified MTLS endpoint
248+
* configured
249+
*/
250+
public TransportChannelProvider withMtlsEndpoint(String mtlsEndpoint) {
251+
return toBuilder().setMtlsEndpoint(mtlsEndpoint).build();
252+
}
253+
228254
/** @deprecated Please modify pool settings via {@link #toBuilder()} */
229255
@Deprecated
230256
@Override
@@ -410,6 +436,76 @@ ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSec
410436
return null;
411437
}
412438

439+
@VisibleForTesting
440+
boolean isGoogleS2AEnabled() {
441+
String S2AEnv = envProvider.getenv(S2A_ENV_ENABLE_USE_S2A);
442+
boolean isS2AEnv = Boolean.parseBoolean(S2AEnv);
443+
if (isS2AEnv) {
444+
return true;
445+
}
446+
return false;
447+
}
448+
449+
@VisibleForTesting
450+
boolean shouldUseS2A() {
451+
// If EXPERIMENTAL_GOOGLE_API_USE_S2A is not set to true, skip S2A.
452+
if (!isGoogleS2AEnabled()) {
453+
return false;
454+
}
455+
// If {@link mtlsEndpoint} is not set, skip S2A. S2A is also skipped when there is endpoint
456+
// override. Endpoint override is respected when the {@link endpoint} is resolved via AIP#4114,
457+
// see EndpointContext.java
458+
if (endpoint != mtlsEndpoint) {
459+
return false;
460+
}
461+
return true;
462+
}
463+
464+
@VisibleForTesting
465+
ChannelCredentials createMtlsToS2AChannelCredentials() throws IOException {
466+
if (!isOnComputeEngine()) {
467+
// Currently, MTLS to MDS is only available on GCE. See:
468+
// https://cloud.google.com/compute/docs/metadata/overview#https-mds
469+
return null;
470+
}
471+
File privateKeyFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY);
472+
File certChainFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY);
473+
File trustBundleFile = new File(MTLS_MDS_ROOT);
474+
if (!privateKeyFile.isFile() || !certChainFile.isFile() || !trustBundleFile.isFile()) {
475+
return null;
476+
}
477+
return TlsChannelCredentials.newBuilder()
478+
.keyManager(privateKeyFile, certChainFile)
479+
.trustManager(trustBundleFile)
480+
.build();
481+
}
482+
483+
@VisibleForTesting
484+
ChannelCredentials createS2ASecuredChannelCredentials() {
485+
S2A s2aUtils = S2A.newBuilder().build();
486+
String plaintextAddress = s2aUtils.getPlaintextS2AAddress();
487+
String mtlsAddress = s2aUtils.getMtlsS2AAddress();
488+
if (!mtlsAddress.isEmpty()) {
489+
try {
490+
// Try to connect to S2A using mTLS.
491+
ChannelCredentials mtlsToS2AChannelCredentials = createMtlsToS2AChannelCredentials();
492+
if (mtlsToS2AChannelCredentials != null) {
493+
return S2AChannelCredentials.newBuilder(mtlsAddress, mtlsToS2AChannelCredentials).build();
494+
}
495+
} catch (IOException ignore) {
496+
// Fallback to plaintext connection to S2A.
497+
}
498+
}
499+
500+
if (!plaintextAddress.isEmpty()) {
501+
// Fallback to plaintext connection to S2A.
502+
return S2AChannelCredentials.newBuilder(plaintextAddress, InsecureChannelCredentials.create())
503+
.build();
504+
}
505+
506+
return null;
507+
}
508+
413509
private ManagedChannel createSingleChannel() throws IOException {
414510
GrpcHeaderInterceptor headerInterceptor =
415511
new GrpcHeaderInterceptor(headersWithDuplicatesRemoved);
@@ -447,16 +543,30 @@ private ManagedChannel createSingleChannel() throws IOException {
447543
builder.keepAliveTime(DIRECT_PATH_KEEP_ALIVE_TIME_SECONDS, TimeUnit.SECONDS);
448544
builder.keepAliveTimeout(DIRECT_PATH_KEEP_ALIVE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
449545
} else {
546+
// Try and create credentials via DCA. See https://google.aip.dev/auth/4114.
450547
ChannelCredentials channelCredentials;
451548
try {
452549
channelCredentials = createMtlsChannelCredentials();
453550
} catch (GeneralSecurityException e) {
454551
throw new IOException(e);
455552
}
456553
if (channelCredentials != null) {
554+
// Create the channel using channel credentials created via DCA.
457555
builder = Grpc.newChannelBuilder(endpoint, channelCredentials);
458556
} else {
459-
builder = ManagedChannelBuilder.forAddress(serviceAddress, port);
557+
// Could not create channel credentials via DCA. In accordance with
558+
// https://google.aip.dev/auth/4115, if credentials not available through
559+
// DCA, try mTLS with credentials held by the S2A (Secure Session Agent).
560+
if (shouldUseS2A()) {
561+
channelCredentials = createS2ASecuredChannelCredentials();
562+
}
563+
if (channelCredentials != null) {
564+
// Create the channel using S2A-secured channel credentials.
565+
builder = Grpc.newChannelBuilder(mtlsEndpoint, channelCredentials);
566+
} else {
567+
// Use default if we cannot initialize channel credentials via DCA or S2A.
568+
builder = ManagedChannelBuilder.forAddress(serviceAddress, port);
569+
}
460570
}
461571
}
462572
// google-c2p resolver requires service config lookup
@@ -604,6 +714,7 @@ public static final class Builder {
604714
private Executor executor;
605715
private HeaderProvider headerProvider;
606716
private String endpoint;
717+
private String mtlsEndpoint;
607718
private EnvironmentProvider envProvider;
608719
private MtlsProvider mtlsProvider = new MtlsProvider();
609720
@Nullable private GrpcInterceptorProvider interceptorProvider;
@@ -632,6 +743,7 @@ private Builder(InstantiatingGrpcChannelProvider provider) {
632743
this.executor = provider.executor;
633744
this.headerProvider = provider.headerProvider;
634745
this.endpoint = provider.endpoint;
746+
this.mtlsEndpoint = provider.mtlsEndpoint;
635747
this.envProvider = provider.envProvider;
636748
this.interceptorProvider = provider.interceptorProvider;
637749
this.maxInboundMessageSize = provider.maxInboundMessageSize;
@@ -700,6 +812,11 @@ public Builder setEndpoint(String endpoint) {
700812
return this;
701813
}
702814

815+
public Builder setMtlsEndpoint(String mtlsEndpoint) {
816+
this.mtlsEndpoint = mtlsEndpoint;
817+
return this;
818+
}
819+
703820
@VisibleForTesting
704821
Builder setMtlsProvider(MtlsProvider mtlsProvider) {
705822
this.mtlsProvider = mtlsProvider;

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

+10
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,21 @@ public boolean needsEndpoint() {
101101
return false;
102102
}
103103

104+
@Override
105+
public boolean needsMtlsEndpoint() {
106+
return false;
107+
}
108+
104109
@Override
105110
public TransportChannelProvider withEndpoint(String endpoint) {
106111
throw new UnsupportedOperationException("LocalChannelProvider doesn't need an endpoint");
107112
}
108113

114+
@Override
115+
public TransportChannelProvider withMtlsEndpoint(String mtlsEndpoint) {
116+
throw new UnsupportedOperationException("LocalChannelProvider doesn't need an mtlsEndpoint");
117+
}
118+
109119
@Override
110120
@BetaApi("The surface for customizing pool size is not stable yet and may change in the future.")
111121
public boolean acceptsPoolSize() {

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

+11
Original file line numberDiff line numberDiff line change
@@ -119,11 +119,22 @@ public boolean needsEndpoint() {
119119
return endpoint == null;
120120
}
121121

122+
@Override
123+
public boolean needsMtlsEndpoint() {
124+
return false;
125+
}
126+
122127
@Override
123128
public TransportChannelProvider withEndpoint(String endpoint) {
124129
return toBuilder().setEndpoint(endpoint).build();
125130
}
126131

132+
@Override
133+
public TransportChannelProvider withMtlsEndpoint(String mtlsEndpoint) {
134+
throw new UnsupportedOperationException(
135+
"InstantiatingHttpJsonChannelProvider doesn't need an mtlsEndpoint");
136+
}
137+
127138
/** @deprecated REST transport channel doesn't support channel pooling */
128139
@Deprecated
129140
@Override

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

+5
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,9 @@
106106
<className>com/google/api/gax/batching/Batcher</className>
107107
<method>*</method>
108108
</difference>
109+
<difference>
110+
<differenceType>7012</differenceType>
111+
<className>com/google/api/gax/rpc/TransportChannelProvider</className>
112+
<method>*</method>
113+
</difference>
109114
</differences>

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

+4
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,7 @@ public static ClientContext create(StubSettings settings) throws IOException {
178178
// A valid EndpointContext should have been created in the StubSettings
179179
EndpointContext endpointContext = settings.getEndpointContext();
180180
String endpoint = endpointContext.resolvedEndpoint();
181+
String mtlsEndpoint = settings.getMtlsEndpoint();
181182
Credentials credentials = getCredentials(settings);
182183
// check if need to adjust credentials/endpoint/endpointContext for GDC-H
183184
String settingsGdchApiAudience = settings.getGdchApiAudience();
@@ -222,6 +223,9 @@ public static ClientContext create(StubSettings settings) throws IOException {
222223
if (transportChannelProvider.needsEndpoint()) {
223224
transportChannelProvider = transportChannelProvider.withEndpoint(endpoint);
224225
}
226+
if (transportChannelProvider.needsMtlsEndpoint()) {
227+
transportChannelProvider = transportChannelProvider.withMtlsEndpoint(mtlsEndpoint);
228+
}
225229
TransportChannel transportChannel = transportChannelProvider.getTransportChannel();
226230

227231
ApiCallContext defaultCallContext =

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

+11
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,23 @@ public boolean needsEndpoint() {
8383
return false;
8484
}
8585

86+
@Override
87+
public boolean needsMtlsEndpoint() {
88+
return false;
89+
}
90+
8691
@Override
8792
public TransportChannelProvider withEndpoint(String endpoint) {
8893
throw new UnsupportedOperationException(
8994
"FixedTransportChannelProvider doesn't need an endpoint");
9095
}
9196

97+
@Override
98+
public TransportChannelProvider withMtlsEndpoint(String mtlsEndpoint) {
99+
throw new UnsupportedOperationException(
100+
"FixedTransportChannelProvider doesn't need an mtlsEndpoint");
101+
}
102+
92103
/** @deprecated FixedTransportChannelProvider doesn't support ChannelPool configuration */
93104
@Deprecated
94105
@Override

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

+10
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,23 @@ public interface TransportChannelProvider {
9090
/** True if the TransportProvider has no endpoint set. */
9191
boolean needsEndpoint();
9292

93+
/** True if the TransportProvider has no mtlsEndpoint set. */
94+
boolean needsMtlsEndpoint();
95+
9396
/**
9497
* Sets the endpoint to use when constructing a new {@link TransportChannel}.
9598
*
9699
* <p>This method should only be called if {@link #needsEndpoint()} returns true.
97100
*/
98101
TransportChannelProvider withEndpoint(String endpoint);
99102

103+
/**
104+
* Sets the mTLS endpoint when constructing a new {@link TransportChannel}.
105+
*
106+
* <p>This method should only be called if {@link #needsMtlsEndpoint()} returns true.
107+
*/
108+
TransportChannelProvider withMtlsEndpoint(String mtlsEndpoint);
109+
100110
/**
101111
* Reports whether this provider allows pool size customization.
102112
*

0 commit comments

Comments
 (0)