Skip to content

Commit

Permalink
Xds: Implement using system root trust CA for TLS server authenticati…
Browse files Browse the repository at this point in the history
…on (#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.
  • Loading branch information
kannanjgithub authored Oct 25, 2024
1 parent 370e7ce commit 0b2c17d
Show file tree
Hide file tree
Showing 13 changed files with 342 additions and 86 deletions.
10 changes: 6 additions & 4 deletions xds/src/main/java/io/grpc/xds/EnvoyServerProtoData.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

package io.grpc.xds;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
Expand All @@ -41,13 +43,13 @@ private EnvoyServerProtoData() {
}

public abstract static class BaseTlsContext {
@Nullable protected final CommonTlsContext commonTlsContext;
protected final CommonTlsContext commonTlsContext;

protected BaseTlsContext(@Nullable CommonTlsContext commonTlsContext) {
this.commonTlsContext = commonTlsContext;
protected BaseTlsContext(CommonTlsContext commonTlsContext) {
this.commonTlsContext = checkNotNull(commonTlsContext, "commonTlsContext cannot be null.");
}

@Nullable public CommonTlsContext getCommonTlsContext() {
public CommonTlsContext getCommonTlsContext() {
return commonTlsContext;
}

Expand Down
11 changes: 9 additions & 2 deletions xds/src/main/java/io/grpc/xds/XdsClusterResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,15 @@
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
import io.grpc.LoadBalancerRegistry;
import io.grpc.NameResolver;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.ServiceConfigUtil;
import io.grpc.internal.ServiceConfigUtil.LbConfig;
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.XdsClusterResource.CdsUpdate;
import io.grpc.xds.client.XdsClient.ResourceUpdate;
import io.grpc.xds.client.XdsResourceType;
import io.grpc.xds.internal.security.CommonTlsContextUtil;
import java.util.List;
import java.util.Locale;
import java.util.Set;
Expand All @@ -57,6 +59,9 @@ class XdsClusterResource extends XdsResourceType<CdsUpdate> {
!Strings.isNullOrEmpty(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST"))
? Boolean.parseBoolean(System.getenv("GRPC_EXPERIMENTAL_ENABLE_LEAST_REQUEST"))
: Boolean.parseBoolean(System.getProperty("io.grpc.xds.experimentalEnableLeastRequest"));
@VisibleForTesting
public static boolean enableSystemRootCerts =
GrpcUtil.getFlag("GRPC_EXPERIMENTAL_XDS_SYSTEM_ROOT_CERTS", false);

@VisibleForTesting
static final String AGGREGATE_CLUSTER_TYPE_NAME = "envoy.clusters.aggregate";
Expand Down Expand Up @@ -430,9 +435,11 @@ static void validateCommonTlsContext(
}
String rootCaInstanceName = getRootCertInstanceName(commonTlsContext);
if (rootCaInstanceName == null) {
if (!server) {
if (!server && (!enableSystemRootCerts
|| !CommonTlsContextUtil.isUsingSystemRootCerts(commonTlsContext))) {
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 {
if (certProviderInstances == null || !certProviderInstances.contains(rootCaInstanceName)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

package io.grpc.xds.internal.security;

import static com.google.common.base.Preconditions.checkNotNull;

import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
import io.grpc.xds.internal.security.ReferenceCountingMap.ValueFactory;
Expand All @@ -44,17 +42,9 @@ final class ClientSslContextProviderFactory
/** Creates an SslContextProvider from the given UpstreamTlsContext. */
@Override
public SslContextProvider create(UpstreamTlsContext upstreamTlsContext) {
checkNotNull(upstreamTlsContext, "upstreamTlsContext");
checkNotNull(
upstreamTlsContext.getCommonTlsContext(),
"upstreamTlsContext should have CommonTlsContext");
if (CommonTlsContextUtil.hasCertProviderInstance(
upstreamTlsContext.getCommonTlsContext())) {
return certProviderClientSslContextProviderFactory.getProvider(
upstreamTlsContext,
bootstrapInfo.node().toEnvoyProtoNode(),
bootstrapInfo.certProviders());
}
throw new UnsupportedOperationException("Unsupported configurations in UpstreamTlsContext!");
return certProviderClientSslContextProviderFactory.getProvider(
upstreamTlsContext,
bootstrapInfo.node().toEnvoyProtoNode(),
bootstrapInfo.certProviders());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public final class CommonTlsContextUtil {

private CommonTlsContextUtil() {}

static boolean hasCertProviderInstance(CommonTlsContext commonTlsContext) {
public static boolean hasCertProviderInstance(CommonTlsContext commonTlsContext) {
if (commonTlsContext == null) {
return false;
}
Expand Down Expand Up @@ -65,4 +65,15 @@ public static CommonTlsContext.CertificateProviderInstance convert(
.setInstanceName(pluginInstance.getInstanceName())
.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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@

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.extensions.transport_sockets.tls.v3.CertificateValidationContext;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.CommonTlsContext;
Expand Down Expand Up @@ -46,7 +44,7 @@ final class CertProviderClientSslContextProvider extends CertProviderSslContextP
node,
certProviders,
certInstance,
checkNotNull(rootCertInstance, "Client SSL requires rootCertInstance"),
rootCertInstance,
staticCertValidationContext,
upstreamTlsContext,
certificateProviderStore);
Expand All @@ -56,12 +54,15 @@ final class CertProviderClientSslContextProvider extends CertProviderSslContextP
protected final SslContextBuilder getSslContextBuilder(
CertificateValidationContext certificateValidationContextdationContext)
throws CertStoreException {
SslContextBuilder sslContextBuilder =
GrpcSslContexts.forClient()
.trustManager(
new XdsTrustManagerFactory(
savedTrustedRoots.toArray(new X509Certificate[0]),
certificateValidationContextdationContext));
SslContextBuilder sslContextBuilder = GrpcSslContexts.forClient();
// Null rootCertInstance implies hasSystemRootCerts because of the check in
// CertProviderClientSslContextProviderFactory.
if (rootCertInstance != null) {
sslContextBuilder.trustManager(
new XdsTrustManagerFactory(
savedTrustedRoots.toArray(new X509Certificate[0]),
certificateValidationContextdationContext));
}
if (isMtls()) {
sslContextBuilder.keyManager(savedKey, savedCertChain);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.grpc.Internal;
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
import io.grpc.xds.client.Bootstrapper.CertificateProviderInfo;
import io.grpc.xds.internal.security.CommonTlsContextUtil;
import io.grpc.xds.internal.security.SslContextProvider;
import java.util.Map;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -64,13 +65,17 @@ public SslContextProvider getProvider(
= CertProviderSslContextProvider.getRootCertProviderInstance(commonTlsContext);
CommonTlsContext.CertificateProviderInstance certInstance
= CertProviderSslContextProvider.getCertProviderInstance(commonTlsContext);
return new CertProviderClientSslContextProvider(
node,
certProviders,
certInstance,
rootCertInstance,
staticCertValidationContext,
upstreamTlsContext,
certificateProviderStore);
if (CommonTlsContextUtil.hasCertProviderInstance(upstreamTlsContext.getCommonTlsContext())
|| CommonTlsContextUtil.isUsingSystemRootCerts(upstreamTlsContext.getCommonTlsContext())) {
return new CertProviderClientSslContextProvider(
node,
certProviders,
certInstance,
rootCertInstance,
staticCertValidationContext,
upstreamTlsContext,
certificateProviderStore);
}
throw new UnsupportedOperationException("Unsupported configurations in UpstreamTlsContext!");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ abstract class CertProviderSslContextProvider extends DynamicSslContextProvider
@Nullable private final CertificateProviderStore.Handle certHandle;
@Nullable private final CertificateProviderStore.Handle rootCertHandle;
@Nullable private final CertificateProviderInstance certInstance;
@Nullable private final CertificateProviderInstance rootCertInstance;
@Nullable protected final CertificateProviderInstance rootCertInstance;
@Nullable protected PrivateKey savedKey;
@Nullable protected List<X509Certificate> savedCertChain;
@Nullable protected List<X509Certificate> savedTrustedRoots;
private final boolean isUsingSystemRootCerts;

protected CertProviderSslContextProvider(
Node node,
Expand Down Expand Up @@ -83,6 +84,8 @@ protected CertProviderSslContextProvider(
} else {
rootCertHandle = null;
}
this.isUsingSystemRootCerts = rootCertInstance == null
&& CommonTlsContextUtil.isUsingSystemRootCerts(tlsContext.getCommonTlsContext());
}

private static CertificateProviderInfo getCertProviderConfig(
Expand Down Expand Up @@ -151,7 +154,7 @@ public final void updateTrustedRoots(List<X509Certificate> trustedRoots) {

private void updateSslContextWhenReady() {
if (isMtls()) {
if (savedKey != null && savedTrustedRoots != null) {
if (savedKey != null && (savedTrustedRoots != null || isUsingSystemRootCerts)) {
updateSslContext();
clearKeysAndCerts();
}
Expand All @@ -175,7 +178,7 @@ private void clearKeysAndCerts() {
}

protected final boolean isMtls() {
return certInstance != null && rootCertInstance != null;
return certInstance != null && (rootCertInstance != null || isUsingSystemRootCerts);
}

protected final boolean isClientSideTls() {
Expand Down
93 changes: 90 additions & 3 deletions xds/src/test/java/io/grpc/xds/GrpcXdsClientImplDataTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -173,18 +173,21 @@ public class GrpcXdsClientImplDataTest {
private final FilterRegistry filterRegistry = FilterRegistry.getDefaultRegistry();
private boolean originalEnableRouteLookup;
private boolean originalEnableLeastRequest;
private boolean originalEnableUseSystemRootCerts;

@Before
public void setUp() {
originalEnableRouteLookup = XdsRouteConfigureResource.enableRouteLookup;
originalEnableLeastRequest = XdsClusterResource.enableLeastRequest;
originalEnableUseSystemRootCerts = XdsClusterResource.enableSystemRootCerts;
assertThat(originalEnableLeastRequest).isFalse();
}

@After
public void tearDown() {
XdsRouteConfigureResource.enableRouteLookup = originalEnableRouteLookup;
XdsClusterResource.enableLeastRequest = originalEnableLeastRequest;
XdsClusterResource.enableSystemRootCerts = originalEnableUseSystemRootCerts;
}

@Test
Expand Down Expand Up @@ -2503,7 +2506,8 @@ public void validateCommonTlsContext_validationContext() throws ResourceInvalidE
.setValidationContext(CertificateValidationContext.getDefaultInstance())
.build();
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);
}

Expand Down Expand Up @@ -2613,6 +2617,87 @@ public void validateCommonTlsContext_validationContextProviderInstance()
.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
@SuppressWarnings("deprecation")
public void validateCommonTlsContext_validationContextProviderInstance_absentInBootstrapFile()
Expand Down Expand Up @@ -2674,7 +2759,8 @@ public void validateCommonTlsContext_combinedValidationContext_isRequiredForClie
CommonTlsContext commonTlsContext = CommonTlsContext.newBuilder()
.build();
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);
}

Expand All @@ -2687,7 +2773,8 @@ public void validateCommonTlsContext_combinedValidationContextWithoutCertProvide
.build();
thrown.expect(ResourceInvalidException.class);
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);
}

Expand Down
3 changes: 2 additions & 1 deletion xds/src/test/java/io/grpc/xds/GrpcXdsClientImplTestBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -2217,7 +2217,8 @@ public void cdsResponseErrorHandling_badUpstreamTlsContext() {
String errorMsg = "CDS response Cluster 'cluster.googleapis.com' validation error: "
+ "Cluster cluster.googleapis.com: malformed UpstreamTlsContext: "
+ "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));
verify(cdsResourceWatcher).onError(errorCaptor.capture());
verifyStatusWithNodeId(errorCaptor.getValue(), Code.UNAVAILABLE, errorMsg);
Expand Down
Loading

0 comments on commit 0b2c17d

Please sign in to comment.