xds: add CertProviderServerSslContextProvider support (#7331)

This commit is contained in:
sanjaypujare 2020-08-17 17:06:29 -07:00 committed by GitHub
parent 39c49b0408
commit ee9109eced
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 439 additions and 7 deletions

View File

@ -0,0 +1,126 @@
/*
* Copyright 2020 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.xds.internal.certprovider;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting;
import io.envoyproxy.envoy.config.core.v3.Node;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext.CombinedCertificateValidationContext;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.xds.Bootstrapper.CertificateProviderInfo;
import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext;
import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory;
import io.netty.handler.ssl.SslContextBuilder;
import java.io.IOException;
import java.security.cert.CertStoreException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Map;
/** A server SslContext provider using CertificateProviderInstance to fetch secrets. */
final class CertProviderServerSslContextProvider extends CertProviderSslContextProvider {
private CertProviderServerSslContextProvider(
Node node,
Map<String, CertificateProviderInfo> certProviders,
CommonTlsContext.CertificateProviderInstance certInstance,
CommonTlsContext.CertificateProviderInstance rootCertInstance,
CertificateValidationContext staticCertValidationContext,
DownstreamTlsContext downstreamTlsContext,
CertificateProviderStore certificateProviderStore) {
super(
node,
certProviders,
checkNotNull(certInstance, "Server SSL requires certInstance"),
rootCertInstance,
staticCertValidationContext,
downstreamTlsContext,
certificateProviderStore);
}
@Override
protected final SslContextBuilder getSslContextBuilder(
CertificateValidationContext certificateValidationContextdationContext)
throws CertStoreException, CertificateException, IOException {
SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(savedKey, savedCertChain);
setClientAuthValues(
sslContextBuilder,
isMtls()
? new SdsTrustManagerFactory(
savedTrustedRoots.toArray(new X509Certificate[0]),
certificateValidationContextdationContext)
: null);
sslContextBuilder = GrpcSslContexts.configure(sslContextBuilder);
return sslContextBuilder;
}
/** Creates CertProviderServerSslContextProvider. */
static final class Factory {
private static final Factory DEFAULT_INSTANCE =
new Factory(CertificateProviderStore.getInstance());
private final CertificateProviderStore certificateProviderStore;
@VisibleForTesting Factory(CertificateProviderStore certificateProviderStore) {
this.certificateProviderStore = certificateProviderStore;
}
static Factory getInstance() {
return DEFAULT_INSTANCE;
}
CertProviderServerSslContextProvider getProvider(
DownstreamTlsContext downstreamTlsContext,
Node node,
Map<String, CertificateProviderInfo> certProviders) {
checkNotNull(downstreamTlsContext, "downstreamTlsContext");
CommonTlsContext commonTlsContext = downstreamTlsContext.getCommonTlsContext();
CommonTlsContext.CertificateProviderInstance rootCertInstance = null;
CertificateValidationContext staticCertValidationContext = null;
if (commonTlsContext.hasCombinedValidationContext()) {
CombinedCertificateValidationContext combinedValidationContext =
commonTlsContext.getCombinedValidationContext();
if (combinedValidationContext.hasValidationContextCertificateProviderInstance()) {
rootCertInstance =
combinedValidationContext.getValidationContextCertificateProviderInstance();
}
if (combinedValidationContext.hasDefaultValidationContext()) {
staticCertValidationContext = combinedValidationContext.getDefaultValidationContext();
}
} else if (commonTlsContext.hasValidationContextCertificateProviderInstance()) {
rootCertInstance = commonTlsContext.getValidationContextCertificateProviderInstance();
} else if (commonTlsContext.hasValidationContext()) {
staticCertValidationContext = commonTlsContext.getValidationContext();
}
CommonTlsContext.CertificateProviderInstance certInstance = null;
if (commonTlsContext.hasTlsCertificateCertificateProviderInstance()) {
certInstance = commonTlsContext.getTlsCertificateCertificateProviderInstance();
}
return new CertProviderServerSslContextProvider(
node,
certProviders,
certInstance,
rootCertInstance,
staticCertValidationContext,
downstreamTlsContext,
certificateProviderStore);
}
}
}

View File

@ -24,6 +24,7 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext;
import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory;
import io.netty.handler.ssl.SslContextBuilder;
import java.io.IOException;
import java.security.cert.CertStoreException;
@ -85,7 +86,11 @@ final class SdsServerSslContextProvider extends SdsSslContextProvider {
tlsCertificate.hasPassword()
? tlsCertificate.getPassword().getInlineString()
: null);
setClientAuthValues(sslContextBuilder, localCertValidationContext);
setClientAuthValues(
sslContextBuilder,
localCertValidationContext != null
? new SdsTrustManagerFactory(localCertValidationContext)
: null);
return sslContextBuilder;
}
}

View File

@ -27,6 +27,7 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsCertificate;
import io.grpc.netty.GrpcSslContexts;
import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext;
import io.grpc.xds.internal.sds.trust.SdsTrustManagerFactory;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import java.io.File;
@ -108,7 +109,8 @@ final class SecretVolumeServerSslContextProvider extends SslContextProvider {
SslContextBuilder sslContextBuilder =
GrpcSslContexts.forServer(
new File(certificateChain), new File(privateKey), privateKeyPassword);
setClientAuthValues(sslContextBuilder, certContext);
setClientAuthValues(
sslContextBuilder, certContext != null ? new SdsTrustManagerFactory(certContext) : null);
return sslContextBuilder.build();
}
}

View File

@ -19,7 +19,6 @@ package io.grpc.xds.internal.sds;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
import io.grpc.xds.EnvoyServerProtoData.BaseTlsContext;
import io.grpc.xds.EnvoyServerProtoData.DownstreamTlsContext;
@ -70,11 +69,11 @@ public abstract class SslContextProvider implements Closeable {
}
protected void setClientAuthValues(
SslContextBuilder sslContextBuilder, CertificateValidationContext localCertValidationContext)
SslContextBuilder sslContextBuilder, SdsTrustManagerFactory sdsTrustManagerFactory)
throws CertificateException, IOException, CertStoreException {
DownstreamTlsContext downstreamTlsContext = getDownstreamTlsContext();
if (localCertValidationContext != null) {
sslContextBuilder.trustManager(new SdsTrustManagerFactory(localCertValidationContext));
if (sdsTrustManagerFactory != null) {
sslContextBuilder.trustManager(sdsTrustManagerFactory);
sslContextBuilder.clientAuth(
downstreamTlsContext.isRequireClientCertificate()
? ClientAuth.REQUIRE

View File

@ -28,6 +28,7 @@ import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FI
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.doChecksOnSslContext;
import static org.junit.Assert.fail;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors;
import io.envoyproxy.envoy.config.core.v3.DataSource;
@ -270,7 +271,7 @@ public class CertProviderClientSslContextProviderTest {
static class QueuedExecutor implements Executor {
/** A list of Runnables to be run in order. */
private final Queue<Runnable> runQueue = new ConcurrentLinkedQueue<>();
@VisibleForTesting final Queue<Runnable> runQueue = new ConcurrentLinkedQueue<>();
@Override
public synchronized void execute(Runnable r) {

View File

@ -0,0 +1,278 @@
/*
* Copyright 2020 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.xds.internal.certprovider;
import static com.google.common.truth.Truth.assertThat;
import static io.grpc.xds.internal.certprovider.CertProviderClientSslContextProviderTest.QueuedExecutor;
import static io.grpc.xds.internal.certprovider.CommonCertProviderTestUtils.getCertFromResourceName;
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CA_PEM_FILE;
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.CLIENT_PEM_FILE;
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_KEY_FILE;
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_0_PEM_FILE;
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_KEY_FILE;
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.SERVER_1_PEM_FILE;
import static io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.doChecksOnSslContext;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.MoreExecutors;
import io.envoyproxy.envoy.config.core.v3.DataSource;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
import io.grpc.xds.Bootstrapper;
import io.grpc.xds.EnvoyServerProtoData;
import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil;
import io.grpc.xds.internal.sds.CommonTlsContextTestsUtil.TestCallback;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Unit tests for {@link CertProviderServerSslContextProvider}. */
@RunWith(JUnit4.class)
public class CertProviderServerSslContextProviderTest {
CertificateProviderRegistry certificateProviderRegistry;
CertificateProviderStore certificateProviderStore;
private CertProviderServerSslContextProvider.Factory certProviderServerSslContextProviderFactory;
@Before
public void setUp() throws Exception {
certificateProviderRegistry = new CertificateProviderRegistry();
certificateProviderStore = new CertificateProviderStore(certificateProviderRegistry);
certProviderServerSslContextProviderFactory =
new CertProviderServerSslContextProvider.Factory(certificateProviderStore);
}
/** Helper method to build CertProviderServerSslContextProvider. */
private CertProviderServerSslContextProvider getSslContextProvider(
String certInstanceName,
String rootInstanceName,
Bootstrapper.BootstrapInfo bootstrapInfo,
Iterable<String> alpnProtocols,
CertificateValidationContext staticCertValidationContext,
boolean requireClientCert) {
EnvoyServerProtoData.DownstreamTlsContext downstreamTlsContext =
CommonTlsContextTestsUtil.buildDownstreamTlsContextForCertProviderInstance(
certInstanceName,
"cert-default",
rootInstanceName,
"root-default",
alpnProtocols,
staticCertValidationContext,
requireClientCert);
return certProviderServerSslContextProviderFactory.getProvider(
downstreamTlsContext,
bootstrapInfo.getNode().toEnvoyProtoNode(),
bootstrapInfo.getCertProviders());
}
@Test
public void testProviderForServer_mtls() throws Exception {
final CertificateProvider.DistributorWatcher[] watcherCaptor =
new CertificateProvider.DistributorWatcher[1];
TestCertificateProvider.createAndRegisterProviderProvider(
certificateProviderRegistry, watcherCaptor, "testca", 0);
CertProviderServerSslContextProvider provider =
getSslContextProvider(
"gcp_id",
"gcp_id",
CommonCertProviderTestUtils.getTestBootstrapInfo(),
/* alpnProtocols= */ null,
/* staticCertValidationContext= */ null,
/* requireClientCert= */ true);
assertThat(provider.savedKey).isNull();
assertThat(provider.savedCertChain).isNull();
assertThat(provider.savedTrustedRoots).isNull();
assertThat(provider.getSslContext()).isNull();
// now generate cert update
watcherCaptor[0].updateCertificate(
CommonCertProviderTestUtils.getPrivateKey(SERVER_0_KEY_FILE),
ImmutableList.of(getCertFromResourceName(SERVER_0_PEM_FILE)));
assertThat(provider.savedKey).isNotNull();
assertThat(provider.savedCertChain).isNotNull();
assertThat(provider.getSslContext()).isNull();
// now generate root cert update
watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE)));
assertThat(provider.getSslContext()).isNotNull();
assertThat(provider.savedKey).isNull();
assertThat(provider.savedCertChain).isNull();
assertThat(provider.savedTrustedRoots).isNull();
TestCallback testCallback =
CommonTlsContextTestsUtil.getValueThruCallback(provider);
doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
TestCallback testCallback1 =
CommonTlsContextTestsUtil.getValueThruCallback(provider);
assertThat(testCallback1.updatedSslContext).isSameInstanceAs(testCallback.updatedSslContext);
// just do root cert update: sslContext should still be the same
watcherCaptor[0].updateTrustedRoots(
ImmutableList.of(getCertFromResourceName(CLIENT_PEM_FILE)));
assertThat(provider.savedKey).isNull();
assertThat(provider.savedCertChain).isNull();
assertThat(provider.savedTrustedRoots).isNotNull();
testCallback1 = CommonTlsContextTestsUtil.getValueThruCallback(provider);
assertThat(testCallback1.updatedSslContext).isSameInstanceAs(testCallback.updatedSslContext);
// now update id cert: sslContext should be updated i.e.different from the previous one
watcherCaptor[0].updateCertificate(
CommonCertProviderTestUtils.getPrivateKey(SERVER_1_KEY_FILE),
ImmutableList.of(getCertFromResourceName(SERVER_1_PEM_FILE)));
assertThat(provider.savedKey).isNull();
assertThat(provider.savedCertChain).isNull();
assertThat(provider.savedTrustedRoots).isNull();
assertThat(provider.getSslContext()).isNotNull();
testCallback1 = CommonTlsContextTestsUtil.getValueThruCallback(provider);
assertThat(testCallback1.updatedSslContext).isNotSameInstanceAs(testCallback.updatedSslContext);
}
@Test
public void testProviderForServer_queueExecutor() throws Exception {
final CertificateProvider.DistributorWatcher[] watcherCaptor =
new CertificateProvider.DistributorWatcher[1];
TestCertificateProvider.createAndRegisterProviderProvider(
certificateProviderRegistry, watcherCaptor, "testca", 0);
CertProviderServerSslContextProvider provider =
getSslContextProvider(
"gcp_id",
"gcp_id",
CommonCertProviderTestUtils.getTestBootstrapInfo(),
/* alpnProtocols= */ null,
/* staticCertValidationContext= */ null,
/* requireClientCert= */ true);
QueuedExecutor queuedExecutor = new QueuedExecutor();
TestCallback testCallback =
CommonTlsContextTestsUtil.getValueThruCallback(provider, queuedExecutor);
assertThat(queuedExecutor.runQueue).isEmpty();
// now generate cert update
watcherCaptor[0].updateCertificate(
CommonCertProviderTestUtils.getPrivateKey(SERVER_0_KEY_FILE),
ImmutableList.of(getCertFromResourceName(SERVER_0_PEM_FILE)));
assertThat(queuedExecutor.runQueue).isEmpty(); // still empty
// now generate root cert update
watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE)));
assertThat(queuedExecutor.runQueue).hasSize(1);
queuedExecutor.drain();
doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
}
@Test
public void testProviderForServer_tls() throws Exception {
final CertificateProvider.DistributorWatcher[] watcherCaptor =
new CertificateProvider.DistributorWatcher[1];
TestCertificateProvider.createAndRegisterProviderProvider(
certificateProviderRegistry, watcherCaptor, "testca", 0);
CertProviderServerSslContextProvider provider =
getSslContextProvider(
"gcp_id",
/* rootInstanceName= */ null,
CommonCertProviderTestUtils.getTestBootstrapInfo(),
/* alpnProtocols= */ null,
/* staticCertValidationContext= */ null,
/* requireClientCert= */ false);
assertThat(provider.savedKey).isNull();
assertThat(provider.savedCertChain).isNull();
assertThat(provider.savedTrustedRoots).isNull();
assertThat(provider.getSslContext()).isNull();
// now generate cert update
watcherCaptor[0].updateCertificate(
CommonCertProviderTestUtils.getPrivateKey(SERVER_0_KEY_FILE),
ImmutableList.of(getCertFromResourceName(SERVER_0_PEM_FILE)));
assertThat(provider.getSslContext()).isNotNull();
assertThat(provider.savedKey).isNull();
assertThat(provider.savedCertChain).isNull();
assertThat(provider.savedTrustedRoots).isNull();
TestCallback testCallback =
CommonTlsContextTestsUtil.getValueThruCallback(provider);
doChecksOnSslContext(true, testCallback.updatedSslContext, /* expectedApnProtos= */ null);
}
@Test
public void testProviderForServer_sslContextException_onError() throws Exception {
CertificateValidationContext staticCertValidationContext =
CertificateValidationContext.newBuilder()
.setTrustedCa(DataSource.newBuilder().setInlineString("foo"))
.build();
final CertificateProvider.DistributorWatcher[] watcherCaptor =
new CertificateProvider.DistributorWatcher[1];
TestCertificateProvider.createAndRegisterProviderProvider(
certificateProviderRegistry, watcherCaptor, "testca", 0);
CertProviderServerSslContextProvider provider =
getSslContextProvider(
/* certInstanceName= */ "gcp_id",
/* rootInstanceName= */ "gcp_id",
CommonCertProviderTestUtils.getTestBootstrapInfo(),
/* alpnProtocols= */null,
staticCertValidationContext,
/* requireClientCert= */ true);
// now generate cert update
watcherCaptor[0].updateCertificate(
CommonCertProviderTestUtils.getPrivateKey(SERVER_0_KEY_FILE),
ImmutableList.of(getCertFromResourceName(SERVER_0_PEM_FILE)));
TestCallback testCallback = new TestCallback(MoreExecutors.directExecutor());
provider.addCallback(testCallback);
try {
watcherCaptor[0].updateTrustedRoots(ImmutableList.of(getCertFromResourceName(CA_PEM_FILE)));
fail("exception expected");
} catch (RuntimeException expected) {
assertThat(expected)
.hasMessageThat()
.contains("only static certificateValidationContext expected");
}
assertThat(testCallback.updatedThrowable).isNotNull();
assertThat(testCallback.updatedThrowable)
.hasCauseThat()
.hasMessageThat()
.contains("only static certificateValidationContext expected");
}
@Test
public void testProviderForServer_certInstanceNull_expectError() throws Exception {
final CertificateProvider.DistributorWatcher[] watcherCaptor =
new CertificateProvider.DistributorWatcher[1];
TestCertificateProvider.createAndRegisterProviderProvider(
certificateProviderRegistry, watcherCaptor, "testca", 0);
try {
getSslContextProvider(
/* certInstanceName= */ null,
/* rootInstanceName= */ null,
CommonCertProviderTestUtils.getTestBootstrapInfo(),
/* alpnProtocols= */ null,
/* staticCertValidationContext= */ null,
/* requireClientCert= */ false);
fail("exception expected");
} catch (NullPointerException expected) {
assertThat(expected).hasMessageThat().contains("Server SSL requires certInstance");
}
}
}

View File

@ -533,6 +533,27 @@ public class CommonTlsContextTestsUtil {
staticCertValidationContext));
}
/** Helper method to build DownstreamTlsContext for CertProvider tests. */
public static EnvoyServerProtoData.DownstreamTlsContext
buildDownstreamTlsContextForCertProviderInstance(
@Nullable String certInstanceName,
@Nullable String certName,
@Nullable String rootInstanceName,
@Nullable String rootCertName,
Iterable<String> alpnProtocols,
CertificateValidationContext staticCertValidationContext,
boolean requireClientCert) {
return buildInternalDownstreamTlsContext(
buildCommonTlsContextForCertProviderInstance(
certInstanceName,
certName,
rootInstanceName,
rootCertName,
alpnProtocols,
staticCertValidationContext), requireClientCert);
}
/** Perform some simple checks on sslContext. */
public static void doChecksOnSslContext(boolean server, SslContext sslContext,
List<String> expectedApnProtos) {