Xds: Implement using system root trust CA for TLS server authentication (#11470)

Allow using system root certs for server cert validation rather than CA root certs provided by the control plane when the validation context provided by the control plane specifies so.
This commit is contained in:
Kannan J 2024-10-25 14:36:27 +05:30 committed by GitHub
parent 370e7ce27c
commit 0b2c17d0da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 342 additions and 86 deletions

View File

@ -16,6 +16,8 @@
package io.grpc.xds; package io.grpc.xds;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.auto.value.AutoValue; import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
@ -41,13 +43,13 @@ public final class EnvoyServerProtoData {
} }
public abstract static class BaseTlsContext { public abstract static class BaseTlsContext {
@Nullable protected final CommonTlsContext commonTlsContext; protected final CommonTlsContext commonTlsContext;
protected BaseTlsContext(@Nullable CommonTlsContext commonTlsContext) { protected BaseTlsContext(CommonTlsContext commonTlsContext) {
this.commonTlsContext = commonTlsContext; this.commonTlsContext = checkNotNull(commonTlsContext, "commonTlsContext cannot be null.");
} }
@Nullable public CommonTlsContext getCommonTlsContext() { public CommonTlsContext getCommonTlsContext() {
return commonTlsContext; return commonTlsContext;
} }

View File

@ -39,6 +39,7 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValida
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
import io.grpc.LoadBalancerRegistry; import io.grpc.LoadBalancerRegistry;
import io.grpc.NameResolver; import io.grpc.NameResolver;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.ServiceConfigUtil; import io.grpc.internal.ServiceConfigUtil;
import io.grpc.internal.ServiceConfigUtil.LbConfig; import io.grpc.internal.ServiceConfigUtil.LbConfig;
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection; import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
@ -46,6 +47,7 @@ import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.XdsClusterResource.CdsUpdate; import io.grpc.xds.XdsClusterResource.CdsUpdate;
import io.grpc.xds.client.XdsClient.ResourceUpdate; import io.grpc.xds.client.XdsClient.ResourceUpdate;
import io.grpc.xds.client.XdsResourceType; import io.grpc.xds.client.XdsResourceType;
import io.grpc.xds.internal.security.CommonTlsContextUtil;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Set; import java.util.Set;
@ -57,6 +59,9 @@ class XdsClusterResource extends XdsResourceType<CdsUpdate> {
!Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST")) !Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST"))
? Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST")) ? Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST"))
: Boolean.parseBoolean(System.getProperty("io.grpc.xds.experimentalEnableLeastRequest")); : Boolean.parseBoolean(System.getProperty("io.grpc.xds.experimentalEnableLeastRequest"));
@VisibleForTesting
public static boolean enableSystemRootCerts =
GrpcUtil.getFlag("GRPC_EXPERIMENTAL_XDS_SYSTEM_ROOT_CERTS", false);
@VisibleForTesting @VisibleForTesting
static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate"; static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate";
@ -430,9 +435,11 @@ class XdsClusterResource extends XdsResourceType<CdsUpdate> {
} }
String rootCaInstanceName = getRootCertInstanceName(commonTlsContext); String rootCaInstanceName = getRootCertInstanceName(commonTlsContext);
if (rootCaInstanceName == null) { if (rootCaInstanceName == null) {
if (!server) { if (!server && (!enableSystemRootCerts
|| !CommonTlsContextUtil.isUsingSystemRootCerts(commonTlsContext))) {
throw new ResourceInvalidException( throw new ResourceInvalidException(
"ca_certificate_provider_instance is required in upstream-tls-context"); "ca_certificate_provider_instance or system_root_certs is required in "
+ "upstream-tls-context");
} }
} else { } else {
if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) { if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) {

View File

@ -16,8 +16,6 @@
package io.grpc.xds.internal.security; package io.grpc.xds.internal.security;
import static com.google.common.base.Preconditions.checkNotNull;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.client.Bootstrapper.BootstrapInfo; import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
import io.grpc.xds.internal.security.ReferenceCountingMap.ValueFactory; import io.grpc.xds.internal.security.ReferenceCountingMap.ValueFactory;
@ -44,17 +42,9 @@ final class ClientSslContextProviderFactory
/** Creates an SslContextProvider from the given UpstreamTlsContext. */ /** Creates an SslContextProvider from the given UpstreamTlsContext. */
@Override @Override
public SslContextProvider create(UpstreamTlsContext upstreamTlsContext) { public SslContextProvider create(UpstreamTlsContext upstreamTlsContext) {
checkNotNull(upstreamTlsContext, "upstreamTlsContext"); return certProviderClientSslContextProviderFactory.getProvider(
checkNotNull( upstreamTlsContext,
upstreamTlsContext.getCommonTlsContext(), bootstrapInfo.node().toEnvoyProtoNode(),
"upstreamTlsContext should have CommonTlsContext"); bootstrapInfo.certProviders());
if (CommonTlsContextUtil.hasCertProviderInstance(
upstreamTlsContext.getCommonTlsContext())) {
return certProviderClientSslContextProviderFactory.getProvider(
upstreamTlsContext,
bootstrapInfo.node().toEnvoyProtoNode(),
bootstrapInfo.certProviders());
}
throw new UnsupportedOperationException("Unsupported configurations in UpstreamTlsContext!");
} }
} }

View File

@ -25,7 +25,7 @@ public final class CommonTlsContextUtil {
private CommonTlsContextUtil() {} private CommonTlsContextUtil() {}
static boolean hasCertProviderInstance(CommonTlsContext commonTlsContext) { public static boolean hasCertProviderInstance(CommonTlsContext commonTlsContext) {
if (commonTlsContext == null) { if (commonTlsContext == null) {
return false; return false;
} }
@ -65,4 +65,15 @@ public final class CommonTlsContextUtil {
.setInstanceName(pluginInstance.getInstanceName()) .setInstanceName(pluginInstance.getInstanceName())
.setCertificateName(pluginInstance.getCertificateName()).build(); .setCertificateName(pluginInstance.getCertificateName()).build();
} }
public static boolean isUsingSystemRootCerts(CommonTlsContext commonTlsContext) {
if (commonTlsContext.hasCombinedValidationContext()) {
return commonTlsContext.getCombinedValidationContext().getDefaultValidationContext()
.hasSystemRootCerts();
}
if (commonTlsContext.hasValidationContext()) {
return commonTlsContext.getValidationContext().hasSystemRootCerts();
}
return false;
}
} }

View File

@ -16,8 +16,6 @@
package io.grpc.xds.internal.security.certprovider; package io.grpc.xds.internal.security.certprovider;
import static com.google.common.base.Preconditions.checkNotNull;
import io.envoyproxy.envoy.config.core.v3.Node; 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.CertificateValidationContext;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext; import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
@ -46,7 +44,7 @@ final class CertProviderClientSslContextProvider extends CertProviderSslContextP
node, node,
certProviders, certProviders,
certInstance, certInstance,
checkNotNull(rootCertInstance, "Client SSL requires rootCertInstance"), rootCertInstance,
staticCertValidationContext, staticCertValidationContext,
upstreamTlsContext, upstreamTlsContext,
certificateProviderStore); certificateProviderStore);
@ -56,12 +54,15 @@ final class CertProviderClientSslContextProvider extends CertProviderSslContextP
protected final SslContextBuilder getSslContextBuilder( protected final SslContextBuilder getSslContextBuilder(
CertificateValidationContext certificateValidationContextdationContext) CertificateValidationContext certificateValidationContextdationContext)
throws CertStoreException { throws CertStoreException {
SslContextBuilder sslContextBuilder = SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient();
GrpcSslContexts.forClient() // Null rootCertInstance implies hasSystemRootCerts because of the check in
.trustManager( // CertProviderClientSslContextProviderFactory.
new XdsTrustManagerFactory( if (rootCertInstance != null) {
savedTrustedRoots.toArray(new X509Certificate[0]), sslContextBuilder.trustManager(
certificateValidationContextdationContext)); new XdsTrustManagerFactory(
savedTrustedRoots.toArray(new X509Certificate[0]),
certificateValidationContextdationContext));
}
if (isMtls()) { if (isMtls()) {
sslContextBuilder.keyManager(savedKey, savedCertChain); sslContextBuilder.keyManager(savedKey, savedCertChain);
} }

View File

@ -25,6 +25,7 @@ import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
import io.grpc.Internal; import io.grpc.Internal;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext; import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.client.Bootstrapper.CertificateProviderInfo; import io.grpc.xds.client.Bootstrapper.CertificateProviderInfo;
import io.grpc.xds.internal.security.CommonTlsContextUtil;
import io.grpc.xds.internal.security.SslContextProvider; import io.grpc.xds.internal.security.SslContextProvider;
import java.util.Map; import java.util.Map;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -64,13 +65,17 @@ public final class CertProviderClientSslContextProviderFactory {
= CertProviderSslContextProvider.getRootCertProviderInstance(commonTlsContext); = CertProviderSslContextProvider.getRootCertProviderInstance(commonTlsContext);
CommonTlsContext.CertificateProviderInstance certInstance CommonTlsContext.CertificateProviderInstance certInstance
= CertProviderSslContextProvider.getCertProviderInstance(commonTlsContext); = CertProviderSslContextProvider.getCertProviderInstance(commonTlsContext);
return new CertProviderClientSslContextProvider( if (CommonTlsContextUtil.hasCertProviderInstance(upstreamTlsContext.getCommonTlsContext())
node, || CommonTlsContextUtil.isUsingSystemRootCerts(upstreamTlsContext.getCommonTlsContext())) {
certProviders, return new CertProviderClientSslContextProvider(
certInstance, node,
rootCertInstance, certProviders,
staticCertValidationContext, certInstance,
upstreamTlsContext, rootCertInstance,
certificateProviderStore); staticCertValidationContext,
upstreamTlsContext,
certificateProviderStore);
}
throw new UnsupportedOperationException("Unsupported configurations in UpstreamTlsContext!");
} }
} }

View File

@ -37,10 +37,11 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider
@Nullable private final CertificateProviderStore.Handle certHandle; @Nullable private final CertificateProviderStore.Handle certHandle;
@Nullable private final CertificateProviderStore.Handle rootCertHandle; @Nullable private final CertificateProviderStore.Handle rootCertHandle;
@Nullable private final CertificateProviderInstance certInstance; @Nullable private final CertificateProviderInstance certInstance;
@Nullable private final CertificateProviderInstance rootCertInstance; @Nullable protected final CertificateProviderInstance rootCertInstance;
@Nullable protected PrivateKey savedKey; @Nullable protected PrivateKey savedKey;
@Nullable protected List<X509Certificate> savedCertChain; @Nullable protected List<X509Certificate> savedCertChain;
@Nullable protected List<X509Certificate> savedTrustedRoots; @Nullable protected List<X509Certificate> savedTrustedRoots;
private final boolean isUsingSystemRootCerts;
protected CertProviderSslContextProvider( protected CertProviderSslContextProvider(
Node node, Node node,
@ -83,6 +84,8 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider
} else { } else {
rootCertHandle = null; rootCertHandle = null;
} }
this.isUsingSystemRootCerts = rootCertInstance == null
&& CommonTlsContextUtil.isUsingSystemRootCerts(tlsContext.getCommonTlsContext());
} }
private static CertificateProviderInfo getCertProviderConfig( private static CertificateProviderInfo getCertProviderConfig(
@ -151,7 +154,7 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider
private void updateSslContextWhenReady() { private void updateSslContextWhenReady() {
if (isMtls()) { if (isMtls()) {
if (savedKey != null && savedTrustedRoots != null) { if (savedKey != null && (savedTrustedRoots != null || isUsingSystemRootCerts)) {
updateSslContext(); updateSslContext();
clearKeysAndCerts(); clearKeysAndCerts();
} }
@ -175,7 +178,7 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider
} }
protected final boolean isMtls() { protected final boolean isMtls() {
return certInstance != null && rootCertInstance != null; return certInstance != null && (rootCertInstance != null || isUsingSystemRootCerts);
} }
protected final boolean isClientSideTls() { protected final boolean isClientSideTls() {

View File

@ -173,11 +173,13 @@ public class GrpcXdsClientImplDataTest {
private final FilterRegistry filterRegistry = FilterRegistry.getDefaultRegistry(); private final FilterRegistry filterRegistry = FilterRegistry.getDefaultRegistry();
private boolean originalEnableRouteLookup; private boolean originalEnableRouteLookup;
private boolean originalEnableLeastRequest; private boolean originalEnableLeastRequest;
private boolean originalEnableUseSystemRootCerts;
@Before @Before
public void setUp() { public void setUp() {
originalEnableRouteLookup = XdsRouteConfigureResource.enableRouteLookup; originalEnableRouteLookup = XdsRouteConfigureResource.enableRouteLookup;
originalEnableLeastRequest = XdsClusterResource.enableLeastRequest; originalEnableLeastRequest = XdsClusterResource.enableLeastRequest;
originalEnableUseSystemRootCerts = XdsClusterResource.enableSystemRootCerts;
assertThat(originalEnableLeastRequest).isFalse(); assertThat(originalEnableLeastRequest).isFalse();
} }
@ -185,6 +187,7 @@ public class GrpcXdsClientImplDataTest {
public void tearDown() { public void tearDown() {
XdsRouteConfigureResource.enableRouteLookup = originalEnableRouteLookup; XdsRouteConfigureResource.enableRouteLookup = originalEnableRouteLookup;
XdsClusterResource.enableLeastRequest = originalEnableLeastRequest; XdsClusterResource.enableLeastRequest = originalEnableLeastRequest;
XdsClusterResource.enableSystemRootCerts = originalEnableUseSystemRootCerts;
} }
@Test @Test
@ -2503,7 +2506,8 @@ public class GrpcXdsClientImplDataTest {
.setValidationContext(CertificateValidationContext.getDefaultInstance()) .setValidationContext(CertificateValidationContext.getDefaultInstance())
.build(); .build();
thrown.expect(ResourceInvalidException.class); thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("ca_certificate_provider_instance is required in upstream-tls-context"); thrown.expectMessage("ca_certificate_provider_instance or system_root_certs is required "
+ "in upstream-tls-context");
XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
} }
@ -2613,6 +2617,87 @@ public class GrpcXdsClientImplDataTest {
.validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), false); .validateCommonTlsContext(commonTlsContext, ImmutableSet.of("name1", "name2"), false);
} }
@Test
public void
validateCommonTlsContext_combinedValidationContextSystemRootCerts_envVarNotSet_throws() {
XdsClusterResource.enableSystemRootCerts = false;
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
.setDefaultValidationContext(
CertificateValidationContext.newBuilder()
.setSystemRootCerts(
CertificateValidationContext.SystemRootCerts.newBuilder().build())
.build()
)
.build())
.build();
try {
XdsClusterResource
.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(), false);
fail("Expected exception");
} catch (ResourceInvalidException ex) {
assertThat(ex.getMessage()).isEqualTo(
"ca_certificate_provider_instance or system_root_certs is required in"
+ " upstream-tls-context");
}
}
@Test
public void validateCommonTlsContext_combinedValidationContextSystemRootCerts()
throws ResourceInvalidException {
XdsClusterResource.enableSystemRootCerts = true;
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setCombinedValidationContext(
CommonTlsContext.CombinedCertificateValidationContext.newBuilder()
.setDefaultValidationContext(
CertificateValidationContext.newBuilder()
.setSystemRootCerts(
CertificateValidationContext.SystemRootCerts.newBuilder().build())
.build()
)
.build())
.build();
XdsClusterResource
.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(), false);
}
@Test
public void validateCommonTlsContext_validationContextSystemRootCerts_envVarNotSet_throws() {
XdsClusterResource.enableSystemRootCerts = false;
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setValidationContext(
CertificateValidationContext.newBuilder()
.setSystemRootCerts(
CertificateValidationContext.SystemRootCerts.newBuilder().build())
.build())
.build();
try {
XdsClusterResource
.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(), false);
fail("Expected exception");
} catch (ResourceInvalidException ex) {
assertThat(ex.getMessage()).isEqualTo(
"ca_certificate_provider_instance or system_root_certs is required in "
+ "upstream-tls-context");
}
}
@Test
public void validateCommonTlsContext_validationContextSystemRootCerts()
throws ResourceInvalidException {
XdsClusterResource.enableSystemRootCerts = true;
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.setValidationContext(
CertificateValidationContext.newBuilder()
.setSystemRootCerts(
CertificateValidationContext.SystemRootCerts.newBuilder().build())
.build())
.build();
XdsClusterResource
.validateCommonTlsContext(commonTlsContext, ImmutableSet.of(), false);
}
@Test @Test
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public void validateCommonTlsContext_validationContextProviderInstance_absentInBootstrapFile() public void validateCommonTlsContext_validationContextProviderInstance_absentInBootstrapFile()
@ -2674,7 +2759,8 @@ public class GrpcXdsClientImplDataTest {
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder() CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.build(); .build();
thrown.expect(ResourceInvalidException.class); thrown.expect(ResourceInvalidException.class);
thrown.expectMessage("ca_certificate_provider_instance is required in upstream-tls-context"); thrown.expectMessage("ca_certificate_provider_instance or system_root_certs is required "
+ "in upstream-tls-context");
XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
} }
@ -2687,7 +2773,8 @@ public class GrpcXdsClientImplDataTest {
.build(); .build();
thrown.expect(ResourceInvalidException.class); thrown.expect(ResourceInvalidException.class);
thrown.expectMessage( thrown.expectMessage(
"ca_certificate_provider_instance is required in upstream-tls-context"); "ca_certificate_provider_instance or system_root_certs is required in "
+ "upstream-tls-context");
XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false); XdsClusterResource.validateCommonTlsContext(commonTlsContext, null, false);
} }

View File

@ -2217,7 +2217,8 @@ public abstract class GrpcXdsClientImplTestBase {
String errorMsg = "CDS response Cluster 'cluster.googleapis.com' validation error: " String errorMsg = "CDS response Cluster 'cluster.googleapis.com' validation error: "
+ "Cluster cluster.googleapis.com: malformed UpstreamTlsContext: " + "Cluster cluster.googleapis.com: malformed UpstreamTlsContext: "
+ "io.grpc.xds.client.XdsResourceType$ResourceInvalidException: " + "io.grpc.xds.client.XdsResourceType$ResourceInvalidException: "
+ "ca_certificate_provider_instance is required in upstream-tls-context"; + "ca_certificate_provider_instance or system_root_certs is required in "
+ "upstream-tls-context";
call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of(errorMsg)); call.verifyRequestNack(CDS, CDS_RESOURCE, "", "0000", NODE, ImmutableList.of(errorMsg));
verify(cdsResourceWatcher).onError(errorCaptor.capture()); verify(cdsResourceWatcher).onError(errorCaptor.capture());
verifyStatusWithNodeId(errorCaptor.getValue(), Code.UNAVAILABLE, errorMsg); verifyStatusWithNodeId(errorCaptor.getValue(), Code.UNAVAILABLE, errorMsg);

View File

@ -31,6 +31,7 @@ import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.SettableFuture;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CertificateValidationContext;
import io.grpc.Attributes; import io.grpc.Attributes;
import io.grpc.EquivalentAddressGroup; import io.grpc.EquivalentAddressGroup;
import io.grpc.Grpc; import io.grpc.Grpc;
@ -67,10 +68,21 @@ import io.grpc.xds.internal.security.CommonTlsContextTestsUtil;
import io.grpc.xds.internal.security.SslContextProviderSupplier; import io.grpc.xds.internal.security.SslContextProviderSupplier;
import io.grpc.xds.internal.security.TlsContextManagerImpl; import io.grpc.xds.internal.security.TlsContextManagerImpl;
import io.netty.handler.ssl.NotSslRecordException; import io.netty.handler.ssl.NotSslRecordException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
@ -104,7 +116,7 @@ public class XdsSecurityClientServerTest {
private static final String OVERRIDE_AUTHORITY = "foo.test.google.fr"; private static final String OVERRIDE_AUTHORITY = "foo.test.google.fr";
@After @After
public void tearDown() { public void tearDown() throws IOException {
if (fakeNameResolverFactory != null) { if (fakeNameResolverFactory != null) {
NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverFactory); NameResolverRegistry.getDefaultRegistry().deregister(fakeNameResolverFactory);
} }
@ -147,6 +159,99 @@ public class XdsSecurityClientServerTest {
assertThat(unaryRpc(/* requestMessage= */ "buddy", blockingStub)).isEqualTo("Hello buddy"); assertThat(unaryRpc(/* requestMessage= */ "buddy", blockingStub)).isEqualTo("Hello buddy");
} }
/**
* Use system root ca cert for TLS channel - no mTLS.
* Uses common_tls_context.combined_validation_context in upstream_tls_context.
*/
@Test
public void tlsClientServer_useSystemRootCerts_useCombinedValidationContext() throws Exception {
Path trustStoreFilePath = getCacertFilePathForTestCa();
try {
setTrustStoreSystemProperties(trustStoreFilePath.toAbsolutePath().toString());
DownstreamTlsContext downstreamTlsContext =
setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false);
buildServerWithTlsContext(downstreamTlsContext);
UpstreamTlsContext upstreamTlsContext =
setBootstrapInfoAndBuildUpstreamTlsContextForUsingSystemRootCerts(CLIENT_KEY_FILE,
CLIENT_PEM_FILE, true);
SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub =
getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY);
assertThat(unaryRpc(/* requestMessage= */ "buddy", blockingStub)).isEqualTo("Hello buddy");
} finally {
Files.deleteIfExists(trustStoreFilePath);
clearTrustStoreSystemProperties();
}
}
/**
* Use system root ca cert for TLS channel - no mTLS.
* Uses common_tls_context.validation_context in upstream_tls_context.
*/
@Test
public void tlsClientServer_useSystemRootCerts_validationContext() throws Exception {
Path trustStoreFilePath = getCacertFilePathForTestCa().toAbsolutePath();
try {
setTrustStoreSystemProperties(trustStoreFilePath.toAbsolutePath().toString());
DownstreamTlsContext downstreamTlsContext =
setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false);
buildServerWithTlsContext(downstreamTlsContext);
UpstreamTlsContext upstreamTlsContext =
setBootstrapInfoAndBuildUpstreamTlsContextForUsingSystemRootCerts(CLIENT_KEY_FILE,
CLIENT_PEM_FILE, false);
SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub =
getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY);
assertThat(unaryRpc(/* requestMessage= */ "buddy", blockingStub)).isEqualTo("Hello buddy");
} finally {
Files.deleteIfExists(trustStoreFilePath.toAbsolutePath());
clearTrustStoreSystemProperties();
}
}
/**
* Use system root ca cert for TLS channel - mTLS.
* Uses common_tls_context.combined_validation_context in upstream_tls_context.
*/
@Test
public void tlsClientServer_useSystemRootCerts_requireClientAuth() throws Exception {
Path trustStoreFilePath = getCacertFilePathForTestCa().toAbsolutePath();
try {
setTrustStoreSystemProperties(trustStoreFilePath.toAbsolutePath().toString());
DownstreamTlsContext downstreamTlsContext =
setBootstrapInfoAndBuildDownstreamTlsContext(null, null, null, null, false, false);
buildServerWithTlsContext(downstreamTlsContext);
UpstreamTlsContext upstreamTlsContext =
setBootstrapInfoAndBuildUpstreamTlsContextForUsingSystemRootCerts(CLIENT_KEY_FILE,
CLIENT_PEM_FILE, true);
SimpleServiceGrpc.SimpleServiceBlockingStub blockingStub =
getBlockingStub(upstreamTlsContext, /* overrideAuthority= */ OVERRIDE_AUTHORITY);
assertThat(unaryRpc(/* requestMessage= */ "buddy", blockingStub)).isEqualTo("Hello buddy");
} finally {
Files.deleteIfExists(trustStoreFilePath.toAbsolutePath());
clearTrustStoreSystemProperties();
}
}
private Path getCacertFilePathForTestCa()
throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(null, null);
InputStream caCertStream = getClass().getResource("/certs/ca.pem").openStream();
keystore.setCertificateEntry("testca", CertificateFactory.getInstance("X.509")
.generateCertificate(caCertStream));
caCertStream.close();
File trustStoreFile = File.createTempFile("testca-truststore", "jks");
FileOutputStream out = new FileOutputStream(trustStoreFile);
keystore.store(out, "changeit".toCharArray());
out.close();
return trustStoreFile.toPath();
}
@Test @Test
public void requireClientAuth_noClientCert_expectException() public void requireClientAuth_noClientCert_expectException()
throws Exception { throws Exception {
@ -323,6 +428,30 @@ public class XdsSecurityClientServerTest {
.buildUpstreamTlsContext("google_cloud_private_spiffe-client", hasIdentityCert); .buildUpstreamTlsContext("google_cloud_private_spiffe-client", hasIdentityCert);
} }
private UpstreamTlsContext setBootstrapInfoAndBuildUpstreamTlsContextForUsingSystemRootCerts(
String clientKeyFile,
String clientPemFile,
boolean useCombinedValidationContext) {
bootstrapInfoForClient = CommonBootstrapperTestUtils
.buildBootstrapInfo("google_cloud_private_spiffe-client", clientKeyFile, clientPemFile,
CA_PEM_FILE, null, null, null, null);
if (useCombinedValidationContext) {
return CommonTlsContextTestsUtil.buildUpstreamTlsContextForCertProviderInstance(
"google_cloud_private_spiffe-client", "ROOT", null,
null, null,
CertificateValidationContext.newBuilder()
.setSystemRootCerts(
CertificateValidationContext.SystemRootCerts.newBuilder().build())
.build());
}
return CommonTlsContextTestsUtil.buildNewUpstreamTlsContextForCertProviderInstance(
"google_cloud_private_spiffe-client", "ROOT", null,
null, null, CertificateValidationContext.newBuilder()
.setSystemRootCerts(
CertificateValidationContext.SystemRootCerts.newBuilder().build())
.build());
}
private void buildServerWithTlsContext(DownstreamTlsContext downstreamTlsContext) private void buildServerWithTlsContext(DownstreamTlsContext downstreamTlsContext)
throws Exception { throws Exception {
buildServerWithTlsContext(downstreamTlsContext, InsecureServerCredentials.create()); buildServerWithTlsContext(downstreamTlsContext, InsecureServerCredentials.create());
@ -450,6 +579,18 @@ public class XdsSecurityClientServerTest {
return settableFuture; return settableFuture;
} }
private void setTrustStoreSystemProperties(String trustStoreFilePath) {
System.setProperty("javax.net.ssl.trustStore", trustStoreFilePath);
System.setProperty("javax.net.ssl.trustStorePassword", "changeit");
System.setProperty("javax.net.ssl.trustStoreType", "JKS");
}
private void clearTrustStoreSystemProperties() {
System.clearProperty("javax.net.ssl.trustStore");
System.clearProperty("javax.net.ssl.trustStorePassword");
System.clearProperty("javax.net.ssl.trustStoreType");
}
private static class SimpleServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase { private static class SimpleServiceImpl extends SimpleServiceGrpc.SimpleServiceImplBase {
@Override @Override

View File

@ -38,8 +38,6 @@ import io.grpc.xds.internal.security.certprovider.CertificateProviderProvider;
import io.grpc.xds.internal.security.certprovider.CertificateProviderRegistry; import io.grpc.xds.internal.security.certprovider.CertificateProviderRegistry;
import io.grpc.xds.internal.security.certprovider.CertificateProviderStore; import io.grpc.xds.internal.security.certprovider.CertificateProviderStore;
import io.grpc.xds.internal.security.certprovider.TestCertificateProvider; import io.grpc.xds.internal.security.certprovider.TestCertificateProvider;
import java.io.IOException;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -285,22 +283,6 @@ public class ClientSslContextProviderFactoryTest {
verifyWatcher(sslContextProvider, watcherCaptor[0]); verifyWatcher(sslContextProvider, watcherCaptor[0]);
} }
@Test
public void createNullCommonTlsContext_exception() throws IOException {
clientSslContextProviderFactory =
new ClientSslContextProviderFactory(
null, certProviderClientSslContextProviderFactory);
UpstreamTlsContext upstreamTlsContext = new UpstreamTlsContext(null);
try {
clientSslContextProviderFactory.create(upstreamTlsContext);
Assert.fail("no exception thrown");
} catch (NullPointerException expected) {
assertThat(expected)
.hasMessageThat()
.isEqualTo("upstreamTlsContext should have CommonTlsContext");
}
}
static void createAndRegisterProviderProvider( static void createAndRegisterProviderProvider(
CertificateProviderRegistry certificateProviderRegistry, CertificateProviderRegistry certificateProviderRegistry,
final CertificateProvider.DistributorWatcher[] watcherCaptor, final CertificateProvider.DistributorWatcher[] watcherCaptor,

View File

@ -250,22 +250,28 @@ public class CommonTlsContextTestsUtil {
String rootInstanceName, String rootInstanceName,
String rootCertName, String rootCertName,
CertificateValidationContext staticCertValidationContext) { CertificateValidationContext staticCertValidationContext) {
CertificateProviderInstance providerInstance = null;
if (rootInstanceName != null) { if (rootInstanceName != null) {
CertificateProviderInstance providerInstance = providerInstance = CertificateProviderInstance.newBuilder()
CertificateProviderInstance.newBuilder() .setInstanceName(rootInstanceName)
.setInstanceName(rootInstanceName) .setCertificateName(rootCertName)
.setCertificateName(rootCertName) .build();
.build(); }
if (staticCertValidationContext != null) { if (providerInstance != null) {
CombinedCertificateValidationContext combined =
CombinedCertificateValidationContext.newBuilder()
.setDefaultValidationContext(staticCertValidationContext)
.setValidationContextCertificateProviderInstance(providerInstance)
.build();
return builder.setCombinedValidationContext(combined);
}
builder = builder.setValidationContextCertificateProviderInstance(providerInstance); builder = builder.setValidationContextCertificateProviderInstance(providerInstance);
} }
CombinedCertificateValidationContext.Builder combined =
CombinedCertificateValidationContext.newBuilder();
if (providerInstance != null) {
combined = combined.setValidationContextCertificateProviderInstance(providerInstance);
}
if (staticCertValidationContext != null) {
combined = combined.setDefaultValidationContext(staticCertValidationContext);
}
if (combined.hasValidationContextCertificateProviderInstance()
|| combined.hasDefaultValidationContext()) {
builder = builder.setCombinedValidationContext(combined.build());
}
return builder; return builder;
} }
@ -274,19 +280,19 @@ public class CommonTlsContextTestsUtil {
String rootInstanceName, String rootInstanceName,
String rootCertName, String rootCertName,
CertificateValidationContext staticCertValidationContext) { CertificateValidationContext staticCertValidationContext) {
CertificateValidationContext.Builder validationContextBuilder =
staticCertValidationContext != null ? staticCertValidationContext.toBuilder()
: CertificateValidationContext.newBuilder();
if (rootInstanceName != null) { if (rootInstanceName != null) {
CertificateProviderPluginInstance providerInstance = CertificateProviderPluginInstance providerInstance =
CertificateProviderPluginInstance.newBuilder() CertificateProviderPluginInstance.newBuilder()
.setInstanceName(rootInstanceName) .setInstanceName(rootInstanceName)
.setCertificateName(rootCertName) .setCertificateName(rootCertName)
.build(); .build();
CertificateValidationContext.Builder validationContextBuilder = validationContextBuilder = validationContextBuilder.setCaCertificateProviderInstance(
staticCertValidationContext != null ? staticCertValidationContext.toBuilder() providerInstance);
: CertificateValidationContext.newBuilder();
return builder.setValidationContext(
validationContextBuilder.setCaCertificateProviderInstance(providerInstance));
} }
return builder; return builder.setValidationContext(validationContextBuilder);
} }
/** Helper method to build UpstreamTlsContext for CertProvider tests. */ /** Helper method to build UpstreamTlsContext for CertProvider tests. */

View File

@ -338,7 +338,8 @@ public class CertProviderClientSslContextProviderTest {
} }
@Test @Test
public void testProviderForClient_rootInstanceNull_expectError() throws Exception { public void testProviderForClient_rootInstanceNull_and_notUsingSystemRootCerts_expectError()
throws Exception {
final CertificateProvider.DistributorWatcher[] watcherCaptor = final CertificateProvider.DistributorWatcher[] watcherCaptor =
new CertificateProvider.DistributorWatcher[1]; new CertificateProvider.DistributorWatcher[1];
TestCertificateProvider.createAndRegisterProviderProvider( TestCertificateProvider.createAndRegisterProviderProvider(
@ -351,11 +352,30 @@ public class CertProviderClientSslContextProviderTest {
/* alpnProtocols= */ null, /* alpnProtocols= */ null,
/* staticCertValidationContext= */ null); /* staticCertValidationContext= */ null);
fail("exception expected"); fail("exception expected");
} catch (NullPointerException expected) { } catch (UnsupportedOperationException expected) {
assertThat(expected).hasMessageThat().contains("Client SSL requires rootCertInstance"); assertThat(expected).hasMessageThat().contains("Unsupported configurations in "
+ "UpstreamTlsContext!");
} }
} }
@Test
public void testProviderForClient_rootInstanceNull_but_isUsingSystemRootCerts_valid()
throws Exception {
final CertificateProvider.DistributorWatcher[] watcherCaptor =
new CertificateProvider.DistributorWatcher[1];
TestCertificateProvider.createAndRegisterProviderProvider(
certificateProviderRegistry, watcherCaptor, "testca", 0);
getSslContextProvider(
/* certInstanceName= */ null,
/* rootInstanceName= */ null,
CommonBootstrapperTestUtils.getTestBootstrapInfo(),
/* alpnProtocols= */ null,
CertificateValidationContext.newBuilder()
.setSystemRootCerts(
CertificateValidationContext.SystemRootCerts.newBuilder().build())
.build());
}
static class QueuedExecutor implements Executor { static class QueuedExecutor implements Executor {
/** A list of Runnables to be run in order. */ /** A list of Runnables to be run in order. */
@VisibleForTesting final Queue<Runnable> runQueue = new ConcurrentLinkedQueue<>(); @VisibleForTesting final Queue<Runnable> runQueue = new ConcurrentLinkedQueue<>();