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: Add experimental S2A integration in client libraries grpc transport #3326

Merged
merged 59 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
aebd139
bump grpc versions.
rmehta19 Oct 29, 2024
b456935
bump auth to local SNAPSHOT.
rmehta19 Oct 29, 2024
3510643
implementation.
rmehta19 Nov 1, 2024
e799526
tests.
rmehta19 Nov 1, 2024
70ae8d0
use endpoint override.
rmehta19 Nov 1, 2024
7ef7313
fix typo
rmehta19 Nov 1, 2024
6d98115
cleanup.
rmehta19 Nov 4, 2024
250fecc
Merge branch 'main' into grpc-channel-using-s2a
rmehta19 Nov 4, 2024
8469a1a
resolve merge conflict with PR#1560
rmehta19 Nov 4, 2024
e880fbe
remove unnecessary dependency.
rmehta19 Nov 4, 2024
28adb31
add mtlsEndpoint to ClientSettings.
rmehta19 Nov 4, 2024
b3e2e06
add logs.
rmehta19 Nov 4, 2024
90892ef
remove ignore rule.
rmehta19 Nov 4, 2024
33b710c
separate into single condition if statements.
rmehta19 Nov 4, 2024
b320cc2
add logic to resolve endpointOverride / customEndpoint in EndpointCon…
rmehta19 Nov 4, 2024
035e17e
add javadocs.
rmehta19 Nov 4, 2024
759c3df
mtlsendpoint changes.
rmehta19 Nov 5, 2024
c096cb7
isS2AEnabled.
rmehta19 Nov 5, 2024
dc4b61e
expose endpointContext.
rmehta19 Nov 5, 2024
d4fcc71
Merge branch 'main' into grpc-channel-using-s2a
rmehta19 Nov 5, 2024
393190a
remove needsEndpointContext.
rmehta19 Nov 6, 2024
30a37c2
reduce nesting.
rmehta19 Nov 6, 2024
c3b93a0
check Strings null or empty.
rmehta19 Nov 6, 2024
18a4cf2
remove unecessary check.
rmehta19 Nov 6, 2024
0897730
set endpointContext in grpc transport provider tests.
rmehta19 Nov 6, 2024
9901656
package private.
rmehta19 Nov 6, 2024
257c515
set System::getenv as envProvider.
rmehta19 Nov 6, 2024
e4565f4
add javadoc.
rmehta19 Nov 6, 2024
33bd7a6
no getters for endpointcontext.
rmehta19 Nov 6, 2024
f9eef5b
nits + package private.
rmehta19 Nov 6, 2024
a7af12e
update javadocs + add mtlsServiceAddress.
rmehta19 Nov 6, 2024
eb70225
add javadocs.
rmehta19 Nov 6, 2024
2a50985
update readme.
rmehta19 Nov 6, 2024
943683d
private + readme updates.
rmehta19 Nov 6, 2024
34f030f
Merge branch 'main' into grpc-channel-using-s2a
rmehta19 Nov 6, 2024
fb3d608
update CLIRR-ignored-differences.
rmehta19 Nov 6, 2024
926faca
mark endpoint as Obsolete.
rmehta19 Nov 6, 2024
4ee2ee9
fix remaining tests to mock endpointContext.resolvedEndpoint().
rmehta19 Nov 6, 2024
2f70bf8
modify obsolete statement.
rmehta19 Nov 6, 2024
cb5768f
Merge branch 'main' into grpc-channel-using-s2a
rmehta19 Nov 7, 2024
c96c760
revert usage of endpointContext.resolvedEndpoint().
rmehta19 Nov 7, 2024
30ff3d6
Merge branch 'main' into grpc-channel-using-s2a
rmehta19 Nov 8, 2024
791ab2c
don't propogate endpointContext.
rmehta19 Nov 11, 2024
a4ee6c5
log as warning.
rmehta19 Nov 11, 2024
6eeccb2
package private + comment on S2A env var.
rmehta19 Nov 11, 2024
03eb991
package private useS2A.
rmehta19 Nov 12, 2024
91175ef
check if files exist.
rmehta19 Nov 12, 2024
04d47a0
default unsupported exception.
rmehta19 Nov 12, 2024
7d7b233
log a warning.
rmehta19 Nov 12, 2024
a42dcbd
pass File to TlsChannelCredentials.
rmehta19 Nov 12, 2024
56551c6
test createPlaintextS2AChannelCredentials.
rmehta19 Nov 12, 2024
1ff7a92
fix file checking logic.
rmehta19 Nov 12, 2024
2958fb4
document mtls mds.
rmehta19 Nov 13, 2024
c9a7edd
rename mtls mds vars to be clear these are filepaths.
rmehta19 Nov 13, 2024
8bf770d
bump auth dep and add some tests for createS2ASecuredChannelCredentials.
rmehta19 Nov 13, 2024
33faba1
Merge branch 'main' into grpc-channel-using-s2a
rmehta19 Nov 13, 2024
924a4e4
fix BUILD.
rmehta19 Nov 13, 2024
b00fd53
Merge branch 'main' into grpc-channel-using-s2a
rmehta19 Nov 14, 2024
ca5be85
fix: Do not build s2a frpm grpc's Bazel target.
blakeli0 Nov 14, 2024
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
2 changes: 1 addition & 1 deletion gapic-generator-java-pom-parent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
consistent across modules in this repository -->
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
<grpc.version>1.68.1</grpc.version>
<google.auth.version>1.29.0</google.auth.version>
<google.auth.version>1.29.1-SNAPSHOT</google.auth.version>
<google.http-client.version>1.45.0</google.http-client.version>
<gson.version>2.11.0</gson.version>
<guava.version>33.3.1-jre</guava.version>
Expand Down
1 change: 1 addition & 0 deletions gax-java/gax-grpc/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ _COMPILE_DEPS = [
"@io_grpc_grpc_netty_shaded//jar",
"@io_grpc_grpc_grpclb//jar",
"@io_grpc_grpc_java//alts:alts",
"@io_grpc_grpc_java//s2a:s2a",
"@io_netty_netty_tcnative_boringssl_static//jar",
"@javax_annotation_javax_annotation_api//jar",
"//gax:gax",
Expand Down
4 changes: 4 additions & 0 deletions gax-java/gax-grpc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-s2a</artifactId>
</dependency>
rmehta19 marked this conversation as resolved.
Show resolved Hide resolved
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,28 @@
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.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.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;
Expand Down Expand Up @@ -99,6 +106,13 @@ 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.
private static final String MTLS_MDS_ROOT = "/run/google-mds-mtls/root.crt";
lqiu96 marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess these are well-known locations on GCE? Do we have any public or internal docs for these locations? If not, where do we get these? What I'm worried is that how do we get notified if they change?

Copy link
Contributor Author

@rmehta19 rmehta19 Nov 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess these are well-known locations on GCE?

Yes

Do we have any public or internal docs for these locations?

I included a link down below in the logic, I've moved it up here as well: https://cloud.google.com/compute/docs/metadata/overview#https-mds. Specifically https://cloud.google.com/compute/docs/metadata/overview#https-mds-root-certs and https://cloud.google.com/compute/docs/metadata/overview#https-mds-client-certs

2958fb4

// 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.
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";
Expand All @@ -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
Expand Down Expand Up @@ -136,6 +151,7 @@ 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.envProvider = builder.envProvider;
this.interceptorProvider = builder.interceptorProvider;
Expand Down Expand Up @@ -225,6 +241,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 +437,113 @@ 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(
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();
}

/**
* 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
S2A s2aUtils = S2A.newBuilder().build();
String plaintextAddress = s2aUtils.getPlaintextS2AAddress();
String mtlsAddress = s2aUtils.getMtlsS2AAddress();
if (Strings.isNullOrEmpty(mtlsAddress)) {
// Fallback to plaintext connection to 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);
File certKeyFile = new File(MTLS_MDS_CERT_CHAIN_AND_KEY);
if (!rootFile.isFile() || !certKeyFile.isFile()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be if (rootFile.isFile() && certKeyFile.isFile())? Or refactor this to

if (!rootFile.isFile() || !certKeyFile.isFile()) {
  return createPlaintextToS2AChannelCredentials(plaintextAddress);
}

to reduce indentation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry this is my bad! Changed to if (rootFile.isFile() && certKeyFile.isFile()). Thanks for catching! 1ff7a92

Copy link
Collaborator

@blakeli0 blakeli0 Nov 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I think that means we don't have any test coverage for createS2ASecuredChannelCredentials() though, and to some extend, also no test coverage for the logics in createSingleChannel().

It's probably hard to test the channels created from createSingleChannel() since there are no getters in ManagedChannel for us to verify it, and that's probably we don't have much test for it in the past either. For the new method createS2ASecuredChannelCredentials(), we could probably extract the file path as parameters of this method so that we can unit test it properly.

// Try to connect to S2A using mTLS.
ChannelCredentials mtlsToS2AChannelCredentials = null;
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 on error.
LOG.log(
Level.WARNING,
"Cannot establish an mTLS connection to S2A due to error loading MTLS to MDS credentials, falling back to plaintext connection to S2A: "
+ ignore.getMessage());
return createPlaintextToS2AChannelCredentials(plaintextAddress);
blakeli0 marked this conversation as resolved.
Show resolved Hide resolved
}
try {
mtlsToS2AChannelCredentials =
createMtlsToS2AChannelCredentials(trustBundle, privateKey, certChain);
} 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 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 @@ -447,16 +581,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
Expand Down Expand Up @@ -604,6 +753,7 @@ public static final class Builder {
private Executor executor;
private HeaderProvider headerProvider;
private String endpoint;
private boolean useS2A;
private EnvironmentProvider envProvider;
private MtlsProvider mtlsProvider = new MtlsProvider();
@Nullable private GrpcInterceptorProvider interceptorProvider;
Expand Down Expand Up @@ -632,6 +782,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 Down Expand Up @@ -700,6 +851,11 @@ public Builder setEndpoint(String endpoint) {
return this;
}

Builder setUseS2A(boolean useS2A) {
this.useS2A = useS2A;
return this;
}

@VisibleForTesting
Builder setMtlsProvider(MtlsProvider mtlsProvider) {
this.mtlsProvider = mtlsProvider;
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
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,10 @@
import com.google.common.truth.Truth;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.TlsChannelCredentials;
import io.grpc.alts.ComputeEngineChannelBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.time.Duration;
import java.util.ArrayList;
Expand Down Expand Up @@ -980,6 +982,53 @@ private FixedHeaderProvider getHeaderProviderWithApiKeyHeader() {
return FixedHeaderProvider.create(header);
}

@Test
void createMtlsToS2AChannelCredentials_missingAllFiles_throws() throws IOException {
InstantiatingGrpcChannelProvider provider =
InstantiatingGrpcChannelProvider.newBuilder().build();
assertThat(provider.createMtlsToS2AChannelCredentials(null, null, null)).isNull();
}

@Test
void createMtlsToS2AChannelCredentials_missingRootFile_throws() throws IOException {
InstantiatingGrpcChannelProvider provider =
InstantiatingGrpcChannelProvider.newBuilder().build();
InputStream privateKey = this.getClass().getClassLoader().getResourceAsStream("client_key.pem");
InputStream certChain = this.getClass().getClassLoader().getResourceAsStream("client_cert.pem");
assertThat(provider.createMtlsToS2AChannelCredentials(null, privateKey, certChain)).isNull();
}

@Test
void createMtlsToS2AChannelCredentials_missingKeyFile_throws() throws IOException {
InstantiatingGrpcChannelProvider provider =
InstantiatingGrpcChannelProvider.newBuilder().build();
InputStream trustBundle = this.getClass().getClassLoader().getResourceAsStream("root_cert.pem");
InputStream certChain = this.getClass().getClassLoader().getResourceAsStream("client_cert.pem");
assertThat(provider.createMtlsToS2AChannelCredentials(trustBundle, null, certChain)).isNull();
}

@Test
void createMtlsToS2AChannelCredentials_missingCertChainFile_throws() throws IOException {
InstantiatingGrpcChannelProvider provider =
InstantiatingGrpcChannelProvider.newBuilder().build();
InputStream trustBundle = this.getClass().getClassLoader().getResourceAsStream("root_cert.pem");
InputStream privateKey = this.getClass().getClassLoader().getResourceAsStream("client_key.pem");
assertThat(provider.createMtlsToS2AChannelCredentials(trustBundle, privateKey, null)).isNull();
}

@Test
void createMtlsToS2AChannelCredentials_success() throws IOException {
InstantiatingGrpcChannelProvider provider =
InstantiatingGrpcChannelProvider.newBuilder().build();
InputStream trustBundle = this.getClass().getClassLoader().getResourceAsStream("root_cert.pem");
InputStream privateKey = this.getClass().getClassLoader().getResourceAsStream("client_key.pem");
InputStream certChain = this.getClass().getClassLoader().getResourceAsStream("client_cert.pem");
assertThat(trustBundle).isNotNull();
assertEquals(
provider.createMtlsToS2AChannelCredentials(trustBundle, privateKey, certChain).getClass(),
TlsChannelCredentials.class);
}

private static class FakeLogHandler extends Handler {

List<LogRecord> records = new ArrayList<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ public TransportChannelProvider withEndpoint(String endpoint) {
throw new UnsupportedOperationException("LocalChannelProvider doesn't need an endpoint");
}

@Override
public TransportChannelProvider withUseS2A(boolean useS2A) {
// Overriden for technical reasons. This method is a no-op for LocalChannelProvider.
return this;
}

@Override
@BetaApi("The surface for customizing pool size is not stable yet and may change in the future.")
public boolean acceptsPoolSize() {
Expand Down
29 changes: 29 additions & 0 deletions gax-java/gax-grpc/src/test/resources/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Regenerate certificates and keys for testing mTLS-S2A
Below are the commands which can be used to regenerate the certs used in tests. This is the same process
used to generate test certs for S2A client in grpc-java: https://github.com/grpc/grpc-java/blob/master/s2a/src/test/resources/README.md

Create root CA

```
openssl req -x509 -sha256 -days 7305 -newkey rsa:2048 -keyout root_key.pem -out
root_cert.pem
```

Generate private key

```
openssl genrsa -out client_key.pem 2048
```

Generate CSR (set Common Name to localhost, leave all
other fields blank)

```
openssl req -key client_key.pem -new -out client.csr -config config.cnf
```

Sign CSR for client

```
openssl x509 -req -CA root_cert.pem -CAkey root_key.pem -in client.csr -out client_cert.pem -days 7305
```
Loading
Loading