-
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 all 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,19 +46,24 @@ | |
import com.google.auth.ApiKeyCredentials; | ||
import com.google.auth.Credentials; | ||
import com.google.auth.oauth2.ComputeEngineCredentials; | ||
import com.google.auth.oauth2.SecureSessionAgent; | ||
import com.google.auth.oauth2.SecureSessionAgentConfig; | ||
import com.google.common.annotations.VisibleForTesting; | ||
import com.google.common.base.Preconditions; | ||
import com.google.common.base.Strings; | ||
import com.google.common.collect.ImmutableList; | ||
import com.google.common.collect.ImmutableMap; | ||
import com.google.common.io.Files; | ||
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.IOException; | ||
import java.nio.charset.StandardCharsets; | ||
|
@@ -99,6 +104,15 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP | |
@VisibleForTesting | ||
static final String DIRECT_PATH_ENV_ENABLE_XDS = "GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS"; | ||
|
||
// The public portion of the mTLS MDS root certificate is stored for performing | ||
// cert verification when establishing an mTLS connection with the MDS. See | ||
// https://cloud.google.com/compute/docs/metadata/overview#https-mds-root-certs | ||
private static final String MTLS_MDS_ROOT_PATH = "/run/google-mds-mtls/root.crt"; | ||
// The mTLS MDS credentials are formatted as the concatenation of a PEM-encoded certificate chain | ||
// followed by a PEM-encoded private key. See | ||
// https://cloud.google.com/compute/docs/metadata/overview#https-mds-client-certs | ||
private static final String MTLS_MDS_CERT_CHAIN_AND_KEY_PATH = "/run/google-mds-mtls/client.key"; | ||
|
||
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"; | ||
|
@@ -107,6 +121,7 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP | |
private final int processorCount; | ||
private final Executor executor; | ||
private final HeaderProvider headerProvider; | ||
private final boolean useS2A; | ||
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 | ||
|
@@ -126,6 +141,7 @@ public final class InstantiatingGrpcChannelProvider implements TransportChannelP | |
@Nullable private final Boolean allowNonDefaultServiceAccount; | ||
@VisibleForTesting final ImmutableMap<String, ?> directPathServiceConfig; | ||
@Nullable private final MtlsProvider mtlsProvider; | ||
@Nullable private final SecureSessionAgent s2aConfigProvider; | ||
@VisibleForTesting final Map<String, String> headersWithDuplicatesRemoved = new HashMap<>(); | ||
|
||
@Nullable | ||
|
@@ -136,7 +152,9 @@ private InstantiatingGrpcChannelProvider(Builder builder) { | |
this.executor = builder.executor; | ||
this.headerProvider = builder.headerProvider; | ||
this.endpoint = builder.endpoint; | ||
this.useS2A = builder.useS2A; | ||
this.mtlsProvider = builder.mtlsProvider; | ||
this.s2aConfigProvider = builder.s2aConfigProvider; | ||
this.envProvider = builder.envProvider; | ||
this.interceptorProvider = builder.interceptorProvider; | ||
this.maxInboundMessageSize = builder.maxInboundMessageSize; | ||
|
@@ -225,6 +243,17 @@ public TransportChannelProvider withEndpoint(String endpoint) { | |
return toBuilder().setEndpoint(endpoint).build(); | ||
} | ||
|
||
/** | ||
* Specify whether or not to use S2A. | ||
* | ||
* @param useS2A | ||
* @return A new {@link InstantiatingGrpcChannelProvider} with useS2A set. | ||
*/ | ||
@Override | ||
public TransportChannelProvider withUseS2A(boolean useS2A) { | ||
return toBuilder().setUseS2A(useS2A).build(); | ||
} | ||
|
||
/** @deprecated Please modify pool settings via {@link #toBuilder()} */ | ||
@Deprecated | ||
@Override | ||
|
@@ -410,6 +439,101 @@ ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSec | |
return null; | ||
} | ||
|
||
/** | ||
* This method creates {@link TlsChannelCredentials} to be used by the client to establish an mTLS | ||
* connection to S2A. Returns null if any of {@param trustBundle}, {@param privateKey} or {@param | ||
* certChain} are missing. | ||
* | ||
* @param trustBundle the trust bundle to be used to establish the client -> S2A mTLS connection | ||
* @param privateKey the client's private key to be used to establish the client -> S2A mtls | ||
* connection | ||
* @param certChain the client's cert chain to be used to establish the client -> S2A mtls | ||
* connection | ||
* @return {@link ChannelCredentials} to use to create an mtls connection between client and S2A | ||
* @throws IOException on error | ||
*/ | ||
@VisibleForTesting | ||
ChannelCredentials createMtlsToS2AChannelCredentials( | ||
File trustBundle, File privateKey, File 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(); | ||
} | ||
|
||
/** | ||
* This method creates {@link ChannelCredentials} to be used by client to establish a plaintext | ||
* connection to S2A. if {@param plaintextAddress} is not present, returns null. | ||
* | ||
* @param plaintextAddress the address to reach S2A which accepts plaintext connections | ||
* @return {@link ChannelCredentials} to use to create a plaintext connection between client and | ||
* S2A | ||
*/ | ||
ChannelCredentials createPlaintextToS2AChannelCredentials(String plaintextAddress) { | ||
if (Strings.isNullOrEmpty(plaintextAddress)) { | ||
return null; | ||
} | ||
return S2AChannelCredentials.newBuilder(plaintextAddress, InsecureChannelCredentials.create()) | ||
.build(); | ||
} | ||
|
||
/** | ||
* This method creates gRPC {@link ChannelCredentials} configured to use S2A to estbalish a mTLS | ||
* connection. First, the address of S2A is discovered by using the {@link S2A} utility to learn | ||
* the {@code mtlsAddress} to reach S2A and the {@code plaintextAddress} to reach S2A. Prefer to | ||
* use the {@code mtlsAddress} address to reach S2A if it is non-empty and the MTLS-MDS | ||
* credentials can successfully be discovered and used to create {@link TlsChannelCredentials}. If | ||
* there is any failure using mTLS-to-S2A, fallback to using a plaintext connection to S2A using | ||
* the {@code plaintextAddress}. If {@code plaintextAddress} is not available, this function | ||
* returns null; in this case S2A will not be used, and a TLS connection to the service will be | ||
* established. | ||
* | ||
* @return {@link ChannelCredentials} configured to use S2A to create mTLS connection to | ||
* mtlsEndpoint. | ||
*/ | ||
ChannelCredentials createS2ASecuredChannelCredentials() { | ||
lqiu96 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
SecureSessionAgentConfig config = s2aConfigProvider.getConfig(); | ||
String plaintextAddress = config.getPlaintextAddress(); | ||
String mtlsAddress = config.getMtlsAddress(); | ||
if (Strings.isNullOrEmpty(mtlsAddress)) { | ||
// Fallback to plaintext connection to S2A. | ||
LOG.log( | ||
Level.INFO, | ||
"Cannot establish an mTLS connection to S2A because autoconfig endpoint did not return a mtls address to reach S2A."); | ||
return createPlaintextToS2AChannelCredentials(plaintextAddress); | ||
} | ||
// 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. | ||
File rootFile = new File(MTLS_MDS_ROOT_PATH); | ||
File certKeyFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY_PATH); | ||
if (rootFile.isFile() && certKeyFile.isFile()) { | ||
// Try to connect to S2A using mTLS. | ||
ChannelCredentials mtlsToS2AChannelCredentials = null; | ||
try { | ||
mtlsToS2AChannelCredentials = | ||
createMtlsToS2AChannelCredentials(rootFile, certKeyFile, certKeyFile); | ||
} catch (IOException ignore) { | ||
// Fallback to plaintext-to-S2A connection on error. | ||
LOG.log( | ||
Level.WARNING, | ||
"Cannot establish an mTLS connection to S2A due to error creating MTLS to MDS TlsChannelCredentials credentials, falling back to plaintext connection to S2A: " | ||
+ ignore.getMessage()); | ||
return createPlaintextToS2AChannelCredentials(plaintextAddress); | ||
} | ||
return S2AChannelCredentials.newBuilder(mtlsAddress, mtlsToS2AChannelCredentials).build(); | ||
} else { | ||
// Fallback to plaintext-to-S2A connection if MTLS-MDS creds do not exist. | ||
LOG.log( | ||
Level.INFO, | ||
blakeli0 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"Cannot establish an mTLS connection to S2A because MTLS to MDS credentials do not exist on filesystem, falling back to plaintext connection to S2A"); | ||
return createPlaintextToS2AChannelCredentials(plaintextAddress); | ||
} | ||
Comment on lines
+528
to
+534
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Perhaps this else block could be an early return instead
would reduce the nesting. Ignore this comment if this was already discussed. PR LGTM There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, I also suggested it in #3326 (comment), but it does not have to block this PR. |
||
} | ||
|
||
private ManagedChannel createSingleChannel() throws IOException { | ||
GrpcHeaderInterceptor headerInterceptor = | ||
new GrpcHeaderInterceptor(headersWithDuplicatesRemoved); | ||
|
@@ -447,16 +571,31 @@ 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 (useS2A) { | ||
channelCredentials = createS2ASecuredChannelCredentials(); | ||
} | ||
if (channelCredentials != null) { | ||
// Create the channel using S2A-secured channel credentials. | ||
// {@code endpoint} is set to mtlsEndpoint in {@link EndpointContext} when useS2A is true. | ||
builder = Grpc.newChannelBuilder(endpoint, 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 | ||
|
@@ -604,7 +743,9 @@ public static final class Builder { | |
private Executor executor; | ||
private HeaderProvider headerProvider; | ||
private String endpoint; | ||
private boolean useS2A; | ||
private EnvironmentProvider envProvider; | ||
private SecureSessionAgent s2aConfigProvider = SecureSessionAgent.create(); | ||
private MtlsProvider mtlsProvider = new MtlsProvider(); | ||
@Nullable private GrpcInterceptorProvider interceptorProvider; | ||
@Nullable private Integer maxInboundMessageSize; | ||
|
@@ -632,6 +773,7 @@ private Builder(InstantiatingGrpcChannelProvider provider) { | |
this.executor = provider.executor; | ||
this.headerProvider = provider.headerProvider; | ||
this.endpoint = provider.endpoint; | ||
this.useS2A = provider.useS2A; | ||
this.envProvider = provider.envProvider; | ||
this.interceptorProvider = provider.interceptorProvider; | ||
this.maxInboundMessageSize = provider.maxInboundMessageSize; | ||
|
@@ -648,6 +790,7 @@ private Builder(InstantiatingGrpcChannelProvider provider) { | |
this.allowNonDefaultServiceAccount = provider.allowNonDefaultServiceAccount; | ||
this.directPathServiceConfig = provider.directPathServiceConfig; | ||
this.mtlsProvider = provider.mtlsProvider; | ||
this.s2aConfigProvider = provider.s2aConfigProvider; | ||
} | ||
|
||
/** | ||
|
@@ -700,12 +843,23 @@ public Builder setEndpoint(String endpoint) { | |
return this; | ||
} | ||
|
||
Builder setUseS2A(boolean useS2A) { | ||
this.useS2A = useS2A; | ||
return this; | ||
} | ||
|
||
@VisibleForTesting | ||
Builder setMtlsProvider(MtlsProvider mtlsProvider) { | ||
this.mtlsProvider = mtlsProvider; | ||
return this; | ||
} | ||
|
||
@VisibleForTesting | ||
Builder setS2AConfigProvider(SecureSessionAgent s2aConfigProvider) { | ||
this.s2aConfigProvider = s2aConfigProvider; | ||
return this; | ||
} | ||
|
||
/** | ||
* Sets the GrpcInterceptorProvider for this TransportChannelProvider. | ||
* | ||
|
Uh oh!
There was an error while loading. Please reload this page.