Skip to content

Commit 3510643

Browse files
committed
implementation.
1 parent b456935 commit 3510643

File tree

11 files changed

+285
-31
lines changed

11 files changed

+285
-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

+138-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,20 @@ public TransportChannelProvider withEndpoint(String endpoint) {
225240
return toBuilder().setEndpoint(endpoint).build();
226241
}
227242

243+
/**
244+
* Specify the MTLS endpoint.
245+
*
246+
* <p>The value of {@code mtlsEndpoint} must be of the form {@code host:port}.
247+
*
248+
* @param mtlsEndpoint
249+
* @return A new {@link InstantiatingGrpcChannelProvider} with the specified MTLS endpoint
250+
* configured
251+
*/
252+
public TransportChannelProvider withMtlsEndpoint(String mtlsEndpoint) {
253+
validateEndpoint(mtlsEndpoint);
254+
return toBuilder().setMtlsEndpoint(mtlsEndpoint).build();
255+
}
256+
228257
/** @deprecated Please modify pool settings via {@link #toBuilder()} */
229258
@Deprecated
230259
@Override
@@ -410,6 +439,83 @@ ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSec
410439
return null;
411440
}
412441

442+
@VisibleForTesting
443+
boolean isGoogleS2AEnabled() {
444+
String S2AEnv = envProvider.getenv(S2A_ENV_ENABLE_USE_S2A);
445+
boolean isS2AEnv = Boolean.parseBoolean(S2AEnv);
446+
if (isS2AEnv) {
447+
return true;
448+
}
449+
return false;
450+
}
451+
452+
@VisibleForTesting
453+
boolean shouldUseS2A() {
454+
// If EXPERIMENTAL_GOOGLE_API_USE_S2A is not set to true, skip S2A.
455+
if (!isGoogleS2AEnabled()) {
456+
return false;
457+
}
458+
459+
// If {@link mtlsEndpoint} is not set, skip S2A. S2A is also skipped when there is endpoint
460+
// override. Endpoint override is respected when the {@link endpoint} is resolved via AIP#4114,
461+
// see EndpointContext.java
462+
if (endpoint != mtlsEndpoint) {
463+
return false;
464+
}
465+
466+
// mTLS via S2A is not supported in any universe other than googleapis.com.
467+
if (!endpoint.contains(Credentials.GOOGLE_DEFAULT_UNIVERSE)) {
468+
return false;
469+
}
470+
471+
return true;
472+
}
473+
474+
@VisibleForTesting
475+
ChannelCredentials createMtlsToS2AChannelCredentials() throws IOException {
476+
if (!isOnComputeEngine()) {
477+
// Currently, MTLS to MDS is only available on GCE. See:
478+
// https://cloud.google.com/compute/docs/metadata/overview#https-mds
479+
return null;
480+
}
481+
File privateKeyFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY);
482+
File certChainFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY);
483+
File trustBundleFile = new File(MTLS_MDS_ROOT);
484+
if (!privateKeyFile.isFile() || !certChainFile.isFile() || !trustBundleFile.isFile()) {
485+
return null;
486+
}
487+
return TlsChannelCredentials.newBuilder()
488+
.keyManager(privateKeyFile, certChainFile)
489+
.trustManager(trustBundleFile)
490+
.build();
491+
}
492+
493+
@VisibleForTesting
494+
ChannelCredentials createS2ASecuredChannelCredentials() {
495+
S2A s2aUtils = S2A.newBuilder().build();
496+
String plaintextAddress = s2aUtils.getPlaintextS2AAddress();
497+
String mtlsAddress = s2aUtils.getMtlsS2AAddress();
498+
if (!mtlsAddress.isEmpty()) {
499+
try {
500+
// Try to connect to S2A using mTLS.
501+
ChannelCredentials mtlsToS2AChannelCredentials = createMtlsToS2AChannelCredentials();
502+
if (mtlsToS2AChannelCredentials != null) {
503+
return S2AChannelCredentials.newBuilder(mtlsAddress, mtlsToS2AChannelCredentials).build();
504+
}
505+
} catch (IOException ignore) {
506+
// Fallback to plaintext connection to S2A.
507+
}
508+
}
509+
510+
if (!plaintextAddress.isEmpty()) {
511+
// Fallback to plaintext connection to S2A.
512+
return S2AChannelCredentials.newBuilder(plaintextAddress, InsecureChannelCredentials.create())
513+
.build();
514+
}
515+
516+
return null;
517+
}
518+
413519
private ManagedChannel createSingleChannel() throws IOException {
414520
GrpcHeaderInterceptor headerInterceptor =
415521
new GrpcHeaderInterceptor(headersWithDuplicatesRemoved);
@@ -447,16 +553,30 @@ private ManagedChannel createSingleChannel() throws IOException {
447553
builder.keepAliveTime(DIRECT_PATH_KEEP_ALIVE_TIME_SECONDS, TimeUnit.SECONDS);
448554
builder.keepAliveTimeout(DIRECT_PATH_KEEP_ALIVE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
449555
} else {
556+
// Try and create credentials via DCA. See https://google.aip.dev/auth/4114.
450557
ChannelCredentials channelCredentials;
451558
try {
452559
channelCredentials = createMtlsChannelCredentials();
453560
} catch (GeneralSecurityException e) {
454561
throw new IOException(e);
455562
}
456563
if (channelCredentials != null) {
564+
// Create the channel using channel credentials created via DCA.
457565
builder = Grpc.newChannelBuilder(endpoint, channelCredentials);
458566
} else {
459-
builder = ManagedChannelBuilder.forAddress(serviceAddress, port);
567+
// Could not create channel credentials via DCA. In accordance with
568+
// https://google.aip.dev/auth/4115, if credentials not available through
569+
// DCA, try mTLS with credentials held by the S2A (Secure Session Agent).
570+
if (shouldUseS2A()) {
571+
channelCredentials = createS2ASecuredChannelCredentials();
572+
}
573+
if (channelCredentials != null) {
574+
// Create the channel using S2A-secured channel credentials.
575+
builder = Grpc.newChannelBuilder(mtlsEndpoint, channelCredentials);
576+
} else {
577+
// Use default if we cannot initialize channel credentials via DCA or S2A.
578+
builder = ManagedChannelBuilder.forAddress(serviceAddress, port);
579+
}
460580
}
461581
}
462582
// google-c2p resolver requires service config lookup
@@ -547,6 +667,11 @@ public String getEndpoint() {
547667
return endpoint;
548668
}
549669

670+
/** The mTLS endpoint. */
671+
public String getMtlsEndpoint() {
672+
return mtlsEndpoint;
673+
}
674+
550675
/** This method is obsolete. Use {@link #getKeepAliveTimeDuration()} instead. */
551676
@ObsoleteApi("Use getKeepAliveTimeDuration() instead")
552677
public org.threeten.bp.Duration getKeepAliveTime() {
@@ -604,6 +729,7 @@ public static final class Builder {
604729
private Executor executor;
605730
private HeaderProvider headerProvider;
606731
private String endpoint;
732+
private String mtlsEndpoint;
607733
private EnvironmentProvider envProvider;
608734
private MtlsProvider mtlsProvider = new MtlsProvider();
609735
@Nullable private GrpcInterceptorProvider interceptorProvider;
@@ -632,6 +758,7 @@ private Builder(InstantiatingGrpcChannelProvider provider) {
632758
this.executor = provider.executor;
633759
this.headerProvider = provider.headerProvider;
634760
this.endpoint = provider.endpoint;
761+
this.mtlsEndpoint = provider.mtlsEndpoint;
635762
this.envProvider = provider.envProvider;
636763
this.interceptorProvider = provider.interceptorProvider;
637764
this.maxInboundMessageSize = provider.maxInboundMessageSize;
@@ -700,6 +827,12 @@ public Builder setEndpoint(String endpoint) {
700827
return this;
701828
}
702829

830+
public Builder setMtlsEndpoint(String mtlsEndpoint) {
831+
validateEndpoint(mtlsEndpoint);
832+
this.mtlsEndpoint = mtlsEndpoint;
833+
return this;
834+
}
835+
703836
@VisibleForTesting
704837
Builder setMtlsProvider(MtlsProvider mtlsProvider) {
705838
this.mtlsProvider = mtlsProvider;
@@ -722,6 +855,10 @@ public String getEndpoint() {
722855
return endpoint;
723856
}
724857

858+
public String getMtlsEndpoint() {
859+
return mtlsEndpoint;
860+
}
861+
725862
/** The maximum message size allowed to be received on the channel. */
726863
public Builder setMaxInboundMessageSize(Integer max) {
727864
this.maxInboundMessageSize = max;

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

0 commit comments

Comments
 (0)