-
Notifications
You must be signed in to change notification settings - Fork 67
feat: Add experimental S2A integration in client libraries grpc transport #3326
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
aebd139
b456935
3510643
e799526
70ae8d0
7ef7313
6d98115
250fecc
8469a1a
e880fbe
28adb31
b3e2e06
90892ef
33b710c
b320cc2
035e17e
759c3df
c096cb7
dc4b61e
d4fcc71
393190a
30a37c2
c3b93a0
18a4cf2
0897730
9901656
257c515
e4565f4
33bd7a6
f9eef5b
a7af12e
eb70225
2a50985
943683d
34f030f
fb3d608
926faca
4ee2ee9
2f70bf8
cb5768f
c96c760
30ff3d6
791ab2c
a4ee6c5
6eeccb2
03eb991
91175ef
04d47a0
7d7b233
a42dcbd
56551c6
1ff7a92
2958fb4
c9a7edd
8bf770d
33faba1
924a4e4
b00fd53
ca5be85
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -46,6 +46,7 @@ | |
| import com.google.auth.ApiKeyCredentials; | ||
| import com.google.auth.Credentials; | ||
| import com.google.auth.oauth2.ComputeEngineCredentials; | ||
| import com.google.auth.oauth2.S2A; | ||
| import com.google.common.annotations.VisibleForTesting; | ||
| import com.google.common.base.Preconditions; | ||
| import com.google.common.collect.ImmutableList; | ||
|
|
@@ -54,13 +55,18 @@ | |
| import io.grpc.CallCredentials; | ||
| import io.grpc.ChannelCredentials; | ||
| import io.grpc.Grpc; | ||
| import io.grpc.InsecureChannelCredentials; | ||
| import io.grpc.ManagedChannel; | ||
| import io.grpc.ManagedChannelBuilder; | ||
| import io.grpc.TlsChannelCredentials; | ||
| import io.grpc.alts.GoogleDefaultChannelCredentials; | ||
| import io.grpc.auth.MoreCallCredentials; | ||
| import io.grpc.s2a.S2AChannelCredentials; | ||
| import java.io.File; | ||
| import java.io.FileInputStream; | ||
| import java.io.FileNotFoundException; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.security.GeneralSecurityException; | ||
| import java.security.KeyStore; | ||
|
|
@@ -99,6 +105,13 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP | |
| @VisibleForTesting | ||
| static final String DIRECT_PATH_ENV_ENABLE_XDS = "GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS"; | ||
|
|
||
| @VisibleForTesting static final String S2A_ENV_ENABLE_USE_S2A = "EXPERIMENTAL_GOOGLE_API_USE_S2A"; | ||
|
|
||
| private static final String MTLS_MDS_ROOT = "/run/google-mds-mtls/root.crt"; | ||
lqiu96 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // The mTLS MDS credentials are formatted as the concatenation of a PEM-encoded certificate chain | ||
| // followed by a PEM-encoded private key. | ||
| private static final String MTLS_MDS_CERT_CHAIN_AND_KEY = "/run/google-mds-mtls/client.key"; | ||
blakeli0 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| static final long DIRECT_PATH_KEEP_ALIVE_TIME_SECONDS = 3600; | ||
| static final long DIRECT_PATH_KEEP_ALIVE_TIMEOUT_SECONDS = 20; | ||
| static final String GCE_PRODUCTION_NAME_PRIOR_2016 = "Google"; | ||
|
|
@@ -108,9 +121,13 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP | |
| private final Executor executor; | ||
| private final HeaderProvider headerProvider; | ||
| private final String endpoint; | ||
| // TODO: remove. envProvider currently provides DirectPath environment variable, and is only used | ||
| // during initial rollout for DirectPath. This provider will be removed once the DirectPath | ||
| // environment is not used. | ||
| private final String mtlsEndpoint; | ||
| private final String endpointOverride; | ||
lqiu96 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // TODO: remove. | ||
| // envProvider currently provides DirectPath and S2A environment variables, and is only used | ||
| // during initial rollout for DirectPath and S2A. This provider will be removed once the | ||
| // DirectPath | ||
| // and S2A environment variables are not used. | ||
| private final EnvironmentProvider envProvider; | ||
| @Nullable private final GrpcInterceptorProvider interceptorProvider; | ||
| @Nullable private final Integer maxInboundMessageSize; | ||
|
|
@@ -136,6 +153,8 @@ private InstantiatingGrpcChannelProvider(Builder builder) { | |
| this.executor = builder.executor; | ||
| this.headerProvider = builder.headerProvider; | ||
| this.endpoint = builder.endpoint; | ||
| this.mtlsEndpoint = builder.mtlsEndpoint; | ||
| this.endpointOverride = builder.endpointOverride; | ||
| this.mtlsProvider = builder.mtlsProvider; | ||
| this.envProvider = builder.envProvider; | ||
| this.interceptorProvider = builder.interceptorProvider; | ||
|
|
@@ -211,6 +230,16 @@ public boolean needsEndpoint() { | |
| return endpoint == null; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean needsMtlsEndpoint() { | ||
| return mtlsEndpoint == null; | ||
| } | ||
|
|
||
| @Override | ||
| public boolean needsEndpointOverride() { | ||
| return endpointOverride == null; | ||
| } | ||
|
|
||
| /** | ||
| * Specify the endpoint the channel should connect to. | ||
| * | ||
|
|
@@ -225,6 +254,40 @@ public TransportChannelProvider withEndpoint(String endpoint) { | |
| return toBuilder().setEndpoint(endpoint).build(); | ||
| } | ||
|
|
||
| /** | ||
| * Specify the MTLS endpoint. | ||
| * | ||
| * <p>The value of {@code mtlsEndpoint} must be of the form {@code host:port}. | ||
| * | ||
| * @param mtlsEndpoint | ||
| * @return A new {@link InstantiatingGrpcChannelProvider} with the specified MTLS endpoint | ||
| * configured | ||
| */ | ||
| @Override | ||
| public TransportChannelProvider withMtlsEndpoint(String mtlsEndpoint) { | ||
| if (!mtlsEndpoint.isEmpty()) { | ||
| validateEndpoint(mtlsEndpoint); | ||
| } | ||
| return toBuilder().setMtlsEndpoint(mtlsEndpoint).build(); | ||
| } | ||
lqiu96 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| /** | ||
| * Specify the endpoint override. | ||
| * | ||
| * <p>The value of {@code endpointOverride} must be of the form {@code host:port}. | ||
| * | ||
| * @param endpointOverride | ||
| * @return A new {@link InstantiatingGrpcChannelProvider} with the specified endpoint Override | ||
| * configured | ||
| */ | ||
| @Override | ||
| public TransportChannelProvider withEndpointOverride(String endpointOverride) { | ||
| if (!endpointOverride.isEmpty()) { | ||
| validateEndpoint(endpointOverride); | ||
| } | ||
| return toBuilder().setEndpointOverride(endpointOverride).build(); | ||
| } | ||
|
|
||
| /** @deprecated Please modify pool settings via {@link #toBuilder()} */ | ||
| @Deprecated | ||
| @Override | ||
|
|
@@ -410,6 +473,89 @@ ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSec | |
| return null; | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
| boolean isGoogleS2AEnabled() { | ||
lqiu96 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| String S2AEnv = envProvider.getenv(S2A_ENV_ENABLE_USE_S2A); | ||
| boolean isS2AEnv = Boolean.parseBoolean(S2AEnv); | ||
| if (isS2AEnv) { | ||
| return true; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
| boolean shouldUseS2A() { | ||
| // If EXPERIMENTAL_GOOGLE_API_USE_S2A is not set to true, skip S2A. | ||
| if (!isGoogleS2AEnabled()) { | ||
| return false; | ||
| } | ||
|
|
||
| // If {@code mtlsEndpoint} is not set, or {@code endpointOverride} is specified, skip S2A. | ||
| if (mtlsEndpoint.isEmpty() || !endpointOverride.isEmpty()) { | ||
|
||
| return false; | ||
| } | ||
rmehta19 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // mTLS via S2A is not supported in any universe other than googleapis.com. | ||
| if (!mtlsEndpoint.contains(Credentials.GOOGLE_DEFAULT_UNIVERSE)) { | ||
| return false; | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
| ChannelCredentials createMtlsToS2AChannelCredentials( | ||
| InputStream trustBundle, InputStream privateKey, InputStream certChain) throws IOException { | ||
| if (trustBundle == null || privateKey == null || certChain == null) { | ||
| return null; | ||
| } | ||
| return TlsChannelCredentials.newBuilder() | ||
| .keyManager(privateKey, certChain) | ||
blakeli0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| .trustManager(trustBundle) | ||
| .build(); | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
| ChannelCredentials createS2ASecuredChannelCredentials() { | ||
lqiu96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| S2A s2aUtils = S2A.newBuilder().build(); | ||
| String plaintextAddress = s2aUtils.getPlaintextS2AAddress(); | ||
| String mtlsAddress = s2aUtils.getMtlsS2AAddress(); | ||
| if (!mtlsAddress.isEmpty()) { | ||
|
||
| // Currently, MTLS to MDS is only available on GCE. See: | ||
| // https://cloud.google.com/compute/docs/metadata/overview#https-mds | ||
| // Try to load MTLS-MDS creds. | ||
| InputStream trustBundle = null; | ||
| InputStream privateKey = null; | ||
| InputStream certChain = null; | ||
| try { | ||
| trustBundle = new FileInputStream(MTLS_MDS_ROOT); | ||
| privateKey = new FileInputStream(MTLS_MDS_CERT_CHAIN_AND_KEY); | ||
| certChain = new FileInputStream(MTLS_MDS_CERT_CHAIN_AND_KEY); | ||
| } catch (FileNotFoundException ignore) { | ||
| // Fallback to plaintext-to-S2A connection. | ||
| } | ||
lqiu96 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| ChannelCredentials mtlsToS2AChannelCredentials = null; | ||
| try { | ||
| // Try to connect to S2A using mTLS. | ||
| mtlsToS2AChannelCredentials = | ||
| createMtlsToS2AChannelCredentials(trustBundle, privateKey, certChain); | ||
| } catch (IOException ignore) { | ||
| // Fallback to plaintext-to-S2A connection. | ||
| } | ||
lqiu96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (mtlsToS2AChannelCredentials != null) { | ||
|
||
| return S2AChannelCredentials.newBuilder(mtlsAddress, mtlsToS2AChannelCredentials).build(); | ||
| } | ||
| } | ||
|
|
||
| if (!plaintextAddress.isEmpty()) { | ||
| // Fallback to plaintext connection to S2A. | ||
| return S2AChannelCredentials.newBuilder(plaintextAddress, InsecureChannelCredentials.create()) | ||
| .build(); | ||
| } | ||
|
||
|
|
||
| return null; | ||
| } | ||
|
|
||
| private ManagedChannel createSingleChannel() throws IOException { | ||
| GrpcHeaderInterceptor headerInterceptor = | ||
| new GrpcHeaderInterceptor(headersWithDuplicatesRemoved); | ||
|
|
@@ -447,16 +593,30 @@ private ManagedChannel createSingleChannel() throws IOException { | |
| builder.keepAliveTime(DIRECT_PATH_KEEP_ALIVE_TIME_SECONDS, TimeUnit.SECONDS); | ||
| builder.keepAliveTimeout(DIRECT_PATH_KEEP_ALIVE_TIMEOUT_SECONDS, TimeUnit.SECONDS); | ||
| } else { | ||
| // Try and create credentials via DCA. See https://google.aip.dev/auth/4114. | ||
| ChannelCredentials channelCredentials; | ||
| try { | ||
| channelCredentials = createMtlsChannelCredentials(); | ||
| } catch (GeneralSecurityException e) { | ||
| throw new IOException(e); | ||
| } | ||
| if (channelCredentials != null) { | ||
| // Create the channel using channel credentials created via DCA. | ||
| builder = Grpc.newChannelBuilder(endpoint, channelCredentials); | ||
| } else { | ||
| builder = ManagedChannelBuilder.forAddress(serviceAddress, port); | ||
| // Could not create channel credentials via DCA. In accordance with | ||
| // https://google.aip.dev/auth/4115, if credentials not available through | ||
| // DCA, try mTLS with credentials held by the S2A (Secure Session Agent). | ||
| if (shouldUseS2A()) { | ||
| channelCredentials = createS2ASecuredChannelCredentials(); | ||
| } | ||
| if (channelCredentials != null) { | ||
| // Create the channel using S2A-secured channel credentials. | ||
| builder = Grpc.newChannelBuilder(mtlsEndpoint, channelCredentials); | ||
| } else { | ||
| // Use default if we cannot initialize channel credentials via DCA or S2A. | ||
| builder = ManagedChannelBuilder.forAddress(serviceAddress, port); | ||
| } | ||
| } | ||
| } | ||
| // google-c2p resolver requires service config lookup | ||
|
|
@@ -547,6 +707,16 @@ public String getEndpoint() { | |
| return endpoint; | ||
| } | ||
|
|
||
| /** The mTLS endpoint. */ | ||
| public String getMtlsEndpoint() { | ||
| return mtlsEndpoint; | ||
| } | ||
|
|
||
| /** The endpoint override */ | ||
| public String getEndpointOverride() { | ||
| return endpointOverride; | ||
| } | ||
|
|
||
| /** This method is obsolete. Use {@link #getKeepAliveTimeDuration()} instead. */ | ||
| @ObsoleteApi("Use getKeepAliveTimeDuration() instead") | ||
| public org.threeten.bp.Duration getKeepAliveTime() { | ||
|
|
@@ -604,6 +774,8 @@ public static final class Builder { | |
| private Executor executor; | ||
| private HeaderProvider headerProvider; | ||
| private String endpoint; | ||
| private String mtlsEndpoint; | ||
| private String endpointOverride; | ||
| private EnvironmentProvider envProvider; | ||
| private MtlsProvider mtlsProvider = new MtlsProvider(); | ||
| @Nullable private GrpcInterceptorProvider interceptorProvider; | ||
|
|
@@ -632,6 +804,8 @@ private Builder(InstantiatingGrpcChannelProvider provider) { | |
| this.executor = provider.executor; | ||
| this.headerProvider = provider.headerProvider; | ||
| this.endpoint = provider.endpoint; | ||
| this.mtlsEndpoint = provider.mtlsEndpoint; | ||
| this.endpointOverride = provider.endpointOverride; | ||
| this.envProvider = provider.envProvider; | ||
| this.interceptorProvider = provider.interceptorProvider; | ||
| this.maxInboundMessageSize = provider.maxInboundMessageSize; | ||
|
|
@@ -700,6 +874,22 @@ public Builder setEndpoint(String endpoint) { | |
| return this; | ||
| } | ||
|
|
||
| public Builder setMtlsEndpoint(String mtlsEndpoint) { | ||
| if (!mtlsEndpoint.isEmpty()) { | ||
| validateEndpoint(mtlsEndpoint); | ||
| } | ||
| this.mtlsEndpoint = mtlsEndpoint; | ||
| return this; | ||
| } | ||
|
|
||
| public Builder setEndpointOverride(String endpointOverride) { | ||
| if (!endpointOverride.isEmpty()) { | ||
| validateEndpoint(endpointOverride); | ||
| } | ||
| this.endpointOverride = endpointOverride; | ||
| return this; | ||
| } | ||
|
|
||
| @VisibleForTesting | ||
| Builder setMtlsProvider(MtlsProvider mtlsProvider) { | ||
| this.mtlsProvider = mtlsProvider; | ||
|
|
@@ -722,6 +912,14 @@ public String getEndpoint() { | |
| return endpoint; | ||
| } | ||
|
|
||
| public String getMtlsEndpoint() { | ||
| return mtlsEndpoint; | ||
| } | ||
|
|
||
| public String getEndpointOverride() { | ||
| return endpointOverride; | ||
| } | ||
|
|
||
| /** The maximum message size allowed to be received on the channel. */ | ||
| public Builder setMaxInboundMessageSize(Integer max) { | ||
| this.maxInboundMessageSize = max; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.