s2a: Combine MtlsToS2ChannelCredentials and S2AChannelCredentials. (#11544)

* Combine MtlsToS2ChannelCredentials and S2AChannelCredentials.

* Check if file exists.

* S2AChannelCredentials API requires credentials used for client-s2a channel.

* remove MtlsToS2A library in BUILD.

* Don't check state twice.

* Don't check for file existence in tests.
This commit is contained in:
Riya Mehta 2024-09-30 09:49:09 -07:00 committed by GitHub
parent 795e2cc3ff
commit a140e1bb0c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 79 additions and 286 deletions

View File

@ -117,19 +117,6 @@ java_library(
],
)
java_library(
name = "mtls_to_s2av2_credentials",
srcs = ["src/main/java/io/grpc/s2a/MtlsToS2AChannelCredentials.java"],
visibility = ["//visibility:public"],
deps = [
":s2a_channel_pool",
":s2av2_credentials",
"//api",
"//util",
artifact("com.google.guava:guava"),
],
)
# bazel only accepts proto import with absolute path.
genrule(
name = "protobuf_imports",

View File

@ -1,89 +0,0 @@
/*
* Copyright 2024 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.s2a;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import io.grpc.ChannelCredentials;
import io.grpc.ExperimentalApi;
import io.grpc.TlsChannelCredentials;
import java.io.File;
import java.io.IOException;
/**
* Configures an {@code S2AChannelCredentials.Builder} instance with credentials used to establish a
* connection with the S2A to support talking to the S2A over mTLS.
*/
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/11533")
public final class MtlsToS2AChannelCredentials {
/**
* Creates a {@code S2AChannelCredentials.Builder} builder, that talks to the S2A over mTLS.
*
* @param s2aAddress the address of the S2A server used to secure the connection.
* @param privateKeyPath the path to the private key PEM to use for authenticating to the S2A.
* @param certChainPath the path to the cert chain PEM to use for authenticating to the S2A.
* @param trustBundlePath the path to the trust bundle PEM.
* @return a {@code MtlsToS2AChannelCredentials.Builder} instance.
*/
public static Builder newBuilder(
String s2aAddress, String privateKeyPath, String certChainPath, String trustBundlePath) {
checkArgument(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty.");
checkArgument(!isNullOrEmpty(privateKeyPath), "privateKeyPath must not be null or empty.");
checkArgument(!isNullOrEmpty(certChainPath), "certChainPath must not be null or empty.");
checkArgument(!isNullOrEmpty(trustBundlePath), "trustBundlePath must not be null or empty.");
return new Builder(s2aAddress, privateKeyPath, certChainPath, trustBundlePath);
}
/** Builds an {@code MtlsToS2AChannelCredentials} instance. */
public static final class Builder {
private final String s2aAddress;
private final String privateKeyPath;
private final String certChainPath;
private final String trustBundlePath;
Builder(
String s2aAddress, String privateKeyPath, String certChainPath, String trustBundlePath) {
this.s2aAddress = s2aAddress;
this.privateKeyPath = privateKeyPath;
this.certChainPath = certChainPath;
this.trustBundlePath = trustBundlePath;
}
public S2AChannelCredentials.Builder build() throws IOException {
checkState(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty.");
checkState(!isNullOrEmpty(privateKeyPath), "privateKeyPath must not be null or empty.");
checkState(!isNullOrEmpty(certChainPath), "certChainPath must not be null or empty.");
checkState(!isNullOrEmpty(trustBundlePath), "trustBundlePath must not be null or empty.");
File privateKeyFile = new File(privateKeyPath);
File certChainFile = new File(certChainPath);
File trustBundleFile = new File(trustBundlePath);
ChannelCredentials channelToS2ACredentials =
TlsChannelCredentials.newBuilder()
.keyManager(certChainFile, privateKeyFile)
.trustManager(trustBundleFile)
.build();
return S2AChannelCredentials.newBuilder(s2aAddress)
.setS2AChannelCredentials(channelToS2ACredentials);
}
}
private MtlsToS2AChannelCredentials() {}
}

View File

@ -18,14 +18,12 @@ package io.grpc.s2a;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Strings.isNullOrEmpty;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import io.grpc.Channel;
import io.grpc.ChannelCredentials;
import io.grpc.ExperimentalApi;
import io.grpc.InsecureChannelCredentials;
import io.grpc.internal.ObjectPool;
import io.grpc.internal.SharedResourcePool;
import io.grpc.netty.InternalNettyChannelCredentials;
@ -33,6 +31,7 @@ import io.grpc.netty.InternalProtocolNegotiator;
import io.grpc.s2a.channel.S2AHandshakerServiceChannel;
import io.grpc.s2a.handshaker.S2AIdentity;
import io.grpc.s2a.handshaker.S2AProtocolNegotiatorFactory;
import java.io.IOException;
import javax.annotation.concurrent.NotThreadSafe;
import org.checkerframework.checker.nullness.qual.Nullable;
@ -46,25 +45,27 @@ public final class S2AChannelCredentials {
* Creates a channel credentials builder for establishing an S2A-secured connection.
*
* @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 a {@code S2AChannelCredentials.Builder} instance.
*/
public static Builder newBuilder(String s2aAddress) {
public static Builder newBuilder(String s2aAddress, ChannelCredentials s2aChannelCredentials) {
checkArgument(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty.");
return new Builder(s2aAddress);
checkNotNull(s2aChannelCredentials, "S2A channel credentials must not be null");
return new Builder(s2aAddress, s2aChannelCredentials);
}
/** Builds an {@code S2AChannelCredentials} instance. */
@NotThreadSafe
public static final class Builder {
private final String s2aAddress;
private final ChannelCredentials s2aChannelCredentials;
private ObjectPool<Channel> s2aChannelPool;
private ChannelCredentials s2aChannelCredentials;
private @Nullable S2AIdentity localIdentity = null;
Builder(String s2aAddress) {
Builder(String s2aAddress, ChannelCredentials s2aChannelCredentials) {
this.s2aAddress = s2aAddress;
this.s2aChannelCredentials = s2aChannelCredentials;
this.s2aChannelPool = null;
this.s2aChannelCredentials = InsecureChannelCredentials.create();
}
/**
@ -106,15 +107,7 @@ public final class S2AChannelCredentials {
return this;
}
/** Sets the credentials to be used when connecting to the S2A. */
@CanIgnoreReturnValue
public Builder setS2AChannelCredentials(ChannelCredentials s2aChannelCredentials) {
this.s2aChannelCredentials = s2aChannelCredentials;
return this;
}
public ChannelCredentials build() {
checkState(!isNullOrEmpty(s2aAddress), "S2A address must not be null or empty.");
public ChannelCredentials build() throws IOException {
ObjectPool<Channel> s2aChannelPool =
SharedResourcePool.forResource(
S2AHandshakerServiceChannel.getChannelResource(s2aAddress, s2aChannelCredentials));

View File

@ -1,135 +0,0 @@
/*
* Copyright 2024 The gRPC Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.grpc.s2a;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class MtlsToS2AChannelCredentialsTest {
@Test
public void newBuilder_nullAddress_throwsException() throws Exception {
assertThrows(
IllegalArgumentException.class,
() ->
MtlsToS2AChannelCredentials.newBuilder(
/* s2aAddress= */ null,
/* privateKeyPath= */ "src/test/resources/client_key.pem",
/* certChainPath= */ "src/test/resources/client_cert.pem",
/* trustBundlePath= */ "src/test/resources/root_cert.pem"));
}
@Test
public void newBuilder_nullPrivateKeyPath_throwsException() throws Exception {
assertThrows(
IllegalArgumentException.class,
() ->
MtlsToS2AChannelCredentials.newBuilder(
/* s2aAddress= */ "s2a_address",
/* privateKeyPath= */ null,
/* certChainPath= */ "src/test/resources/client_cert.pem",
/* trustBundlePath= */ "src/test/resources/root_cert.pem"));
}
@Test
public void newBuilder_nullCertChainPath_throwsException() throws Exception {
assertThrows(
IllegalArgumentException.class,
() ->
MtlsToS2AChannelCredentials.newBuilder(
/* s2aAddress= */ "s2a_address",
/* privateKeyPath= */ "src/test/resources/client_key.pem",
/* certChainPath= */ null,
/* trustBundlePath= */ "src/test/resources/root_cert.pem"));
}
@Test
public void newBuilder_nullTrustBundlePath_throwsException() throws Exception {
assertThrows(
IllegalArgumentException.class,
() ->
MtlsToS2AChannelCredentials.newBuilder(
/* s2aAddress= */ "s2a_address",
/* privateKeyPath= */ "src/test/resources/client_key.pem",
/* certChainPath= */ "src/test/resources/client_cert.pem",
/* trustBundlePath= */ null));
}
@Test
public void newBuilder_emptyAddress_throwsException() throws Exception {
assertThrows(
IllegalArgumentException.class,
() ->
MtlsToS2AChannelCredentials.newBuilder(
/* s2aAddress= */ "",
/* privateKeyPath= */ "src/test/resources/client_key.pem",
/* certChainPath= */ "src/test/resources/client_cert.pem",
/* trustBundlePath= */ "src/test/resources/root_cert.pem"));
}
@Test
public void newBuilder_emptyPrivateKeyPath_throwsException() throws Exception {
assertThrows(
IllegalArgumentException.class,
() ->
MtlsToS2AChannelCredentials.newBuilder(
/* s2aAddress= */ "s2a_address",
/* privateKeyPath= */ "",
/* certChainPath= */ "src/test/resources/client_cert.pem",
/* trustBundlePath= */ "src/test/resources/root_cert.pem"));
}
@Test
public void newBuilder_emptyCertChainPath_throwsException() throws Exception {
assertThrows(
IllegalArgumentException.class,
() ->
MtlsToS2AChannelCredentials.newBuilder(
/* s2aAddress= */ "s2a_address",
/* privateKeyPath= */ "src/test/resources/client_key.pem",
/* certChainPath= */ "",
/* trustBundlePath= */ "src/test/resources/root_cert.pem"));
}
@Test
public void newBuilder_emptyTrustBundlePath_throwsException() throws Exception {
assertThrows(
IllegalArgumentException.class,
() ->
MtlsToS2AChannelCredentials.newBuilder(
/* s2aAddress= */ "s2a_address",
/* privateKeyPath= */ "src/test/resources/client_key.pem",
/* certChainPath= */ "src/test/resources/client_cert.pem",
/* trustBundlePath= */ ""));
}
@Test
public void build_s2AChannelCredentials_success() throws Exception {
assertThat(
MtlsToS2AChannelCredentials.newBuilder(
/* s2aAddress= */ "s2a_address",
/* privateKeyPath= */ "src/test/resources/client_key.pem",
/* certChainPath= */ "src/test/resources/client_cert.pem",
/* trustBundlePath= */ "src/test/resources/root_cert.pem")
.build())
.isInstanceOf(S2AChannelCredentials.Builder.class);
}
}

View File

@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import io.grpc.ChannelCredentials;
import io.grpc.InsecureChannelCredentials;
import io.grpc.TlsChannelCredentials;
import java.io.File;
import org.junit.Test;
@ -30,40 +31,51 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public final class S2AChannelCredentialsTest {
@Test
public void newBuilder_nullArgument_throwsException() throws Exception {
assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.newBuilder(null));
public void newBuilder_nullAddress_throwsException() throws Exception {
assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.newBuilder(null,
InsecureChannelCredentials.create()));
}
@Test
public void newBuilder_emptyAddress_throwsException() throws Exception {
assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.newBuilder(""));
assertThrows(IllegalArgumentException.class, () -> S2AChannelCredentials.newBuilder("",
InsecureChannelCredentials.create()));
}
@Test
public void newBuilder_nullChannelCreds_throwsException() throws Exception {
assertThrows(NullPointerException.class, () -> S2AChannelCredentials
.newBuilder("s2a_address", null));
}
@Test
public void setLocalSpiffeId_nullArgument_throwsException() throws Exception {
assertThrows(
NullPointerException.class,
() -> S2AChannelCredentials.newBuilder("s2a_address").setLocalSpiffeId(null));
() -> S2AChannelCredentials.newBuilder("s2a_address",
InsecureChannelCredentials.create()).setLocalSpiffeId(null));
}
@Test
public void setLocalHostname_nullArgument_throwsException() throws Exception {
assertThrows(
NullPointerException.class,
() -> S2AChannelCredentials.newBuilder("s2a_address").setLocalHostname(null));
() -> S2AChannelCredentials.newBuilder("s2a_address",
InsecureChannelCredentials.create()).setLocalHostname(null));
}
@Test
public void setLocalUid_nullArgument_throwsException() throws Exception {
assertThrows(
NullPointerException.class,
() -> S2AChannelCredentials.newBuilder("s2a_address").setLocalUid(null));
() -> S2AChannelCredentials.newBuilder("s2a_address",
InsecureChannelCredentials.create()).setLocalUid(null));
}
@Test
public void build_withLocalSpiffeId_succeeds() throws Exception {
assertThat(
S2AChannelCredentials.newBuilder("s2a_address")
S2AChannelCredentials.newBuilder("s2a_address", InsecureChannelCredentials.create())
.setLocalSpiffeId("spiffe://test")
.build())
.isNotNull();
@ -72,7 +84,7 @@ public final class S2AChannelCredentialsTest {
@Test
public void build_withLocalHostname_succeeds() throws Exception {
assertThat(
S2AChannelCredentials.newBuilder("s2a_address")
S2AChannelCredentials.newBuilder("s2a_address", InsecureChannelCredentials.create())
.setLocalHostname("local_hostname")
.build())
.isNotNull();
@ -80,33 +92,47 @@ public final class S2AChannelCredentialsTest {
@Test
public void build_withLocalUid_succeeds() throws Exception {
assertThat(S2AChannelCredentials.newBuilder("s2a_address").setLocalUid("local_uid").build())
assertThat(S2AChannelCredentials.newBuilder("s2a_address",
InsecureChannelCredentials.create()).setLocalUid("local_uid").build())
.isNotNull();
}
@Test
public void build_withNoLocalIdentity_succeeds() throws Exception {
assertThat(S2AChannelCredentials.newBuilder("s2a_address").build())
assertThat(S2AChannelCredentials.newBuilder("s2a_address",
InsecureChannelCredentials.create()).build())
.isNotNull();
}
@Test
public void build_withTlsChannelCredentials_succeeds() throws Exception {
public void build_withUseMtlsToS2ANoLocalIdentity_success() throws Exception {
ChannelCredentials s2aChannelCredentials = getTlsChannelCredentials();
assertThat(
S2AChannelCredentials.newBuilder("s2a_address")
.setLocalSpiffeId("spiffe://test")
.setS2AChannelCredentials(getTlsChannelCredentials())
S2AChannelCredentials.newBuilder("s2a_address", s2aChannelCredentials)
.build())
.isNotNull();
}
@Test
public void build_withUseMtlsToS2AWithLocalUid_success() throws Exception {
ChannelCredentials s2aChannelCredentials = getTlsChannelCredentials();
assertThat(
S2AChannelCredentials.newBuilder("s2a_address", s2aChannelCredentials)
.setLocalUid("local_uid")
.build())
.isNotNull();
}
private static ChannelCredentials getTlsChannelCredentials() throws Exception {
File clientCert = new File("src/test/resources/client_cert.pem");
File clientKey = new File("src/test/resources/client_key.pem");
File rootCert = new File("src/test/resources/root_cert.pem");
String privateKeyPath = "src/test/resources/client_key.pem";
String certChainPath = "src/test/resources/client_cert.pem";
String trustBundlePath = "src/test/resources/root_cert.pem";
File privateKeyFile = new File(privateKeyPath);
File certChainFile = new File(certChainPath);
File trustBundleFile = new File(trustBundlePath);
return TlsChannelCredentials.newBuilder()
.keyManager(clientCert, clientKey)
.trustManager(rootCert)
.build();
.keyManager(certChainFile, privateKeyFile)
.trustManager(trustBundleFile)
.build();
}
}

View File

@ -21,15 +21,16 @@ import static java.util.concurrent.TimeUnit.SECONDS;
import io.grpc.ChannelCredentials;
import io.grpc.Grpc;
import io.grpc.InsecureChannelCredentials;
import io.grpc.ManagedChannel;
import io.grpc.Server;
import io.grpc.ServerBuilder;
import io.grpc.ServerCredentials;
import io.grpc.TlsChannelCredentials;
import io.grpc.TlsServerCredentials;
import io.grpc.benchmarks.Utils;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.NettyServerBuilder;
import io.grpc.s2a.MtlsToS2AChannelCredentials;
import io.grpc.s2a.S2AChannelCredentials;
import io.grpc.s2a.handshaker.FakeS2AServer;
import io.grpc.stub.StreamObserver;
@ -124,7 +125,8 @@ public final class IntegrationTest {
@Test
public void clientCommunicateUsingS2ACredentials_succeeds() throws Exception {
ChannelCredentials credentials =
S2AChannelCredentials.newBuilder(s2aAddress).setLocalSpiffeId("test-spiffe-id").build();
S2AChannelCredentials.newBuilder(s2aAddress, InsecureChannelCredentials.create())
.setLocalSpiffeId("test-spiffe-id").build();
ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build();
assertThat(doUnaryRpc(channel)).isTrue();
@ -132,7 +134,8 @@ public final class IntegrationTest {
@Test
public void clientCommunicateUsingS2ACredentialsNoLocalIdentity_succeeds() throws Exception {
ChannelCredentials credentials = S2AChannelCredentials.newBuilder(s2aAddress).build();
ChannelCredentials credentials = S2AChannelCredentials.newBuilder(s2aAddress,
InsecureChannelCredentials.create()).build();
ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build();
assertThat(doUnaryRpc(channel)).isTrue();
@ -140,15 +143,22 @@ public final class IntegrationTest {
@Test
public void clientCommunicateUsingMtlsToS2ACredentials_succeeds() throws Exception {
String privateKeyPath = "src/test/resources/client_key.pem";
String certChainPath = "src/test/resources/client_cert.pem";
String trustBundlePath = "src/test/resources/root_cert.pem";
File privateKeyFile = new File(privateKeyPath);
File certChainFile = new File(certChainPath);
File trustBundleFile = new File(trustBundlePath);
ChannelCredentials s2aChannelCredentials =
TlsChannelCredentials.newBuilder()
.keyManager(certChainFile, privateKeyFile)
.trustManager(trustBundleFile)
.build();
ChannelCredentials credentials =
MtlsToS2AChannelCredentials.newBuilder(
/* s2aAddress= */ mtlsS2AAddress,
/* privateKeyPath= */ "src/test/resources/client_key.pem",
/* certChainPath= */ "src/test/resources/client_cert.pem",
/* trustBundlePath= */ "src/test/resources/root_cert.pem")
.build()
.setLocalSpiffeId("test-spiffe-id")
.build();
S2AChannelCredentials.newBuilder(mtlsS2AAddress, s2aChannelCredentials)
.setLocalSpiffeId("test-spiffe-id")
.build();
ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build();
assertThat(doUnaryRpc(channel)).isTrue();
@ -156,7 +166,8 @@ public final class IntegrationTest {
@Test
public void clientCommunicateUsingS2ACredentials_s2AdelayStart_succeeds() throws Exception {
ChannelCredentials credentials = S2AChannelCredentials.newBuilder(s2aDelayAddress).build();
ChannelCredentials credentials = S2AChannelCredentials.newBuilder(s2aDelayAddress,
InsecureChannelCredentials.create()).build();
ManagedChannel channel = Grpc.newChannelBuilder(serverAddress, credentials).build();
FutureTask<Boolean> rpc = new FutureTask<>(() -> doUnaryRpc(channel));