Skip to content
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

feat: revert #3400: reintroduce experimental S2A integration in client libraries grpc transport #3548

Merged
merged 25 commits into from
Jan 24, 2025
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3d99673
initial revert of #3400.
rmehta19 Jan 7, 2025
9b4b405
Path #3401.
rmehta19 Jan 7, 2025
ffa4e7b
Patch #3386.
rmehta19 Jan 7, 2025
b03cb51
change env var name.
rmehta19 Jan 7, 2025
451e31b
fix comment.
rmehta19 Jan 7, 2025
494e828
Merge branch 'main' into s2a-integration-roll-forward
rmehta19 Jan 7, 2025
22c82ad
Merge branch 'main' into s2a-integration-roll-forward
rmehta19 Jan 7, 2025
2ce9003
Merge branch 'main' into s2a-integration-roll-forward
rmehta19 Jan 9, 2025
e19dad4
Merge branch 'main' into s2a-integration-roll-forward
rmehta19 Jan 13, 2025
0807a2e
declare + define env var one line.
rmehta19 Jan 13, 2025
95e70d3
remove duplicate builder initialization.
rmehta19 Jan 13, 2025
b807294
Modify S2A API reflection log message.
rmehta19 Jan 13, 2025
882047c
Merge branch 'main' into s2a-integration-roll-forward
rmehta19 Jan 15, 2025
25445d3
set mtls endpoint for S2A in EndpointContext.
rmehta19 Jan 15, 2025
e921696
address nits in code comment language + formatting.
rmehta19 Jan 16, 2025
9fb0b49
move S2A check in determineEndpoint.
rmehta19 Jan 16, 2025
fd6163a
mark withUseS2A as a Beta API.
rmehta19 Jan 16, 2025
a814a53
remove mtls endpoint from clirr-ignored-differences.
rmehta19 Jan 16, 2025
1703f51
default withUseS2A doesn't throw an exception.
rmehta19 Jan 21, 2025
22562d6
Merge branch 'main' into s2a-integration-roll-forward
rmehta19 Jan 21, 2025
b046dc9
resolve merge conflict.
rmehta19 Jan 22, 2025
6f56cff
move S2A check to end of determineEndpoint.
rmehta19 Jan 23, 2025
ea4e701
update comment above S2A logic in determineEndpoint.
rmehta19 Jan 23, 2025
fa59ce8
Merge branch 'main' into s2a-integration-roll-forward
rmehta19 Jan 23, 2025
aa6ba37
add more detail to S2A comment in determine endpoint.
rmehta19 Jan 23, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions gax-java/gax-grpc/clirr-ignored-differences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,4 @@
<className>com/google/api/gax/grpc/GrpcTransportChannel</className>
<method>boolean isDirectPath()</method>
</difference>
<!-- Ignore this as this was part of s2a-grpc ExperimentalApi revert -->
<difference>
<differenceType>7002</differenceType>
<className>com/google/api/gax/grpc/InstantiatingGrpcChannelProvider</className>
<method>* withUseS2A(*)</method>
</difference>
</differences>
5 changes: 5 additions & 0 deletions gax-java/gax-grpc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@
</dependency>

<!-- test dependencies -->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-s2a</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.google.api.grpc</groupId>
<artifactId>grpc-google-common-protos</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,26 @@
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 java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
Expand Down Expand Up @@ -99,6 +104,19 @@ 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
// {@link <a
// href="https://cloud.google.com/compute/docs/metadata/overview#https-mds-root-certs">this</a>
// for more information.}
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
// {@link <a
// href="https://cloud.google.com/compute/docs/metadata/overview#https-mds-client-certs">this</a>
// for more information.}
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";
Expand All @@ -107,6 +125,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
Expand All @@ -126,6 +145,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
Expand All @@ -135,8 +155,10 @@ private InstantiatingGrpcChannelProvider(Builder builder) {
this.processorCount = builder.processorCount;
this.executor = builder.executor;
this.headerProvider = builder.headerProvider;
this.useS2A = builder.useS2A;
this.endpoint = builder.endpoint;
this.mtlsProvider = builder.mtlsProvider;
this.s2aConfigProvider = builder.s2aConfigProvider;
this.envProvider = builder.envProvider;
this.interceptorProvider = builder.interceptorProvider;
this.maxInboundMessageSize = builder.maxInboundMessageSize;
Expand Down Expand Up @@ -225,6 +247,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
Expand Down Expand Up @@ -410,6 +443,136 @@ ChannelCredentials createMtlsChannelCredentials() throws IOException, GeneralSec
return null;
}

/**
* Create the S2A-Secured Channel credentials. Load the API using reflection. Once the S2A API is
* stable in gRPC-Java, all callers of this method can simply use the S2A APIs directly and this
* method can be deleted.
*
* @param s2aAddress the address of the S2A server used to secure the connection.
* @param s2aChannelCredentials the credentials to be used when connecting to the S2A.
* @return {@code ChannelCredentials} instance.
*/
ChannelCredentials buildS2AChannelCredentials(
String s2aAddress, ChannelCredentials s2aChannelCredentials) {
try {
// Load the S2A API.
Class<?> s2aChannelCreds = Class.forName("io.grpc.s2a.S2AChannelCredentials");
Class<?> s2aChannelCredsBuilder = Class.forName("io.grpc.s2a.S2AChannelCredentials$Builder");

// Load and invoke the S2A API methods.
Class<?>[] partypes = new Class[2];
partypes[0] = String.class;
partypes[1] = ChannelCredentials.class;
Method newBuilder = s2aChannelCreds.getMethod("newBuilder", partypes);
Object arglist[] = new Object[2];
arglist[0] = s2aAddress;
arglist[1] = s2aChannelCredentials;
Object retObjBuilder = newBuilder.invoke(null, arglist);
Method build = s2aChannelCredsBuilder.getMethod("build", null);
Object retObjCreds = build.invoke(retObjBuilder, null);
return (ChannelCredentials) retObjCreds;
} catch (Throwable t) {
LOG.log(
Level.WARNING,
"Falling back to default (TLS without S2A) because S2A APIs cannot be used: "
+ t.getMessage());
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)
.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 buildS2AChannelCredentials(plaintextAddress, InsecureChannelCredentials.create());
}

/**
* 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.
*/
ChannelCredentials createS2ASecuredChannelCredentials() {
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: "
lqiu96 marked this conversation as resolved.
Show resolved Hide resolved
+ ignore.getMessage());
return createPlaintextToS2AChannelCredentials(plaintextAddress);
}
return buildS2AChannelCredentials(mtlsAddress, mtlsToS2AChannelCredentials);
} else {
// Fallback to plaintext-to-S2A connection if MTLS-MDS creds do not exist.
LOG.log(
Level.INFO,
"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);
}
}

private ManagedChannel createSingleChannel() throws IOException {
GrpcHeaderInterceptor headerInterceptor =
new GrpcHeaderInterceptor(headersWithDuplicatesRemoved);
Expand Down Expand Up @@ -449,14 +612,28 @@ private ManagedChannel createSingleChannel() throws IOException {
} else {
ChannelCredentials channelCredentials;
try {
// Try and create credentials via DCA. See https://google.aip.dev/auth/4114.
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.
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
Expand Down Expand Up @@ -604,7 +781,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;
Expand Down Expand Up @@ -632,6 +811,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;
Expand All @@ -648,6 +828,7 @@ private Builder(InstantiatingGrpcChannelProvider provider) {
this.allowNonDefaultServiceAccount = provider.allowNonDefaultServiceAccount;
this.directPathServiceConfig = provider.directPathServiceConfig;
this.mtlsProvider = provider.mtlsProvider;
this.s2aConfigProvider = provider.s2aConfigProvider;
}

/**
Expand Down Expand Up @@ -700,12 +881,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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ void setUp() throws IOException {
TransportChannel transportChannel =
GrpcTransportChannel.newBuilder().setManagedChannel(channel).build();
when(operationsChannelProvider.getTransportChannel()).thenReturn(transportChannel);
when(operationsChannelProvider.withUseS2A(Mockito.any(boolean.class)))
.thenReturn(operationsChannelProvider);

clock = new FakeApiClock(0L);
executor = RecordingScheduler.create(clock);
Expand Down
Loading
Loading