blob: afa57f96c46f3fcea664d41fdcd25465d54d8874 [file] [log] [blame]
/*
* 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.sds;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Strings;
import com.google.common.io.CharStreams;
import com.google.protobuf.BoolValue;
import com.google.protobuf.Struct;
import com.google.protobuf.Value;
import io.envoyproxy.envoy.api.v2.core.ApiConfigSource.ApiType;
import io.envoyproxy.envoy.api.v2.core.GrpcService.GoogleGrpc;
import io.envoyproxy.envoy.config.core.v3.ApiConfigSource;
import io.envoyproxy.envoy.config.core.v3.ConfigSource;
import io.envoyproxy.envoy.config.core.v3.DataSource;
import io.envoyproxy.envoy.config.core.v3.GrpcService;
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.envoyproxy.envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.SdsSecretConfig;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.TlsCertificate;
import io.envoyproxy.envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext;
import io.envoyproxy.envoy.type.matcher.v3.StringMatcher;
import io.grpc.internal.testing.TestUtils;
import io.grpc.xds.EnvoyServerProtoData;
import io.grpc.xds.internal.sds.trust.CertificateUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import javax.annotation.Nullable;
/** Utility class for client and server ssl provider tests. */
public class CommonTlsContextTestsUtil {
public static final String SERVER_0_PEM_FILE = "server0.pem";
public static final String SERVER_0_KEY_FILE = "server0.key";
public static final String SERVER_1_PEM_FILE = "server1.pem";
public static final String SERVER_1_KEY_FILE = "server1.key";
public static final String CLIENT_PEM_FILE = "client.pem";
public static final String CLIENT_KEY_FILE = "client.key";
public static final String CA_PEM_FILE = "ca.pem";
/** Bad/untrusted server certs. */
public static final String BAD_SERVER_PEM_FILE = "badserver.pem";
public static final String BAD_SERVER_KEY_FILE = "badserver.key";
public static final String BAD_CLIENT_PEM_FILE = "badclient.pem";
public static final String BAD_CLIENT_KEY_FILE = "badclient.key";
static io.envoyproxy.envoy.api.v2.auth.SdsSecretConfig buildSdsSecretConfigV2(
String name, String targetUri, String channelType) {
io.envoyproxy.envoy.api.v2.auth.SdsSecretConfig sdsSecretConfig = null;
if (!Strings.isNullOrEmpty(name) && !Strings.isNullOrEmpty(targetUri)) {
sdsSecretConfig =
io.envoyproxy.envoy.api.v2.auth.SdsSecretConfig.newBuilder()
.setName(name)
.setSdsConfig(buildConfigSourceV2(targetUri, channelType))
.build();
}
return sdsSecretConfig;
}
private static SdsSecretConfig
buildSdsSecretConfig(String name, String targetUri, String channelType) {
SdsSecretConfig sdsSecretConfig = null;
if (!Strings.isNullOrEmpty(name) && !Strings.isNullOrEmpty(targetUri)) {
sdsSecretConfig =
SdsSecretConfig.newBuilder()
.setName(name)
.setSdsConfig(buildConfigSource(targetUri, channelType))
.build();
}
return sdsSecretConfig;
}
/**
* Builds a {@link io.envoyproxy.envoy.api.v2.core.ConfigSource} for the given targetUri.
*
* @param channelType specifying "inproc" creates an Inprocess channel for testing.
*/
private static io.envoyproxy.envoy.api.v2.core.ConfigSource buildConfigSourceV2(
String targetUri, String channelType) {
GoogleGrpc.Builder googleGrpcBuilder = GoogleGrpc.newBuilder().setTargetUri(targetUri);
if (channelType != null) {
Struct.Builder structBuilder = Struct.newBuilder()
.putFields("channelType", Value.newBuilder().setStringValue(channelType).build());
googleGrpcBuilder.setConfig(structBuilder.build());
}
return io.envoyproxy.envoy.api.v2.core.ConfigSource.newBuilder()
.setApiConfigSource(
io.envoyproxy.envoy.api.v2.core.ApiConfigSource.newBuilder()
.setApiType(ApiType.GRPC)
.addGrpcServices(
io.envoyproxy.envoy.api.v2.core.GrpcService.newBuilder()
.setGoogleGrpc(googleGrpcBuilder.build())
.build())
.build())
.build();
}
/**
* Builds a {@link ConfigSource} for the given targetUri.
*
* @param channelType specifying "inproc" creates an Inprocess channel for testing.
*/
private static ConfigSource buildConfigSource(String targetUri, String channelType) {
GrpcService.GoogleGrpc.Builder googleGrpcBuilder =
GrpcService.GoogleGrpc.newBuilder().setTargetUri(targetUri);
if (channelType != null) {
Struct.Builder structBuilder = Struct.newBuilder()
.putFields("channelType", Value.newBuilder().setStringValue(channelType).build());
googleGrpcBuilder.setConfig(structBuilder.build());
}
return ConfigSource.newBuilder()
.setApiConfigSource(
ApiConfigSource.newBuilder()
.setApiType(ApiConfigSource.ApiType.GRPC)
.addGrpcServices(GrpcService.newBuilder().setGoogleGrpc(googleGrpcBuilder))
.build())
.build();
}
static CommonTlsContext buildCommonTlsContextFromSdsConfigForValidationContext(
String name, String targetUri, String privateKey, String certChain) {
SdsSecretConfig sdsSecretConfig =
buildSdsSecretConfig(name, targetUri, /* channelType= */ null);
CommonTlsContext.Builder builder =
CommonTlsContext.newBuilder().setValidationContextSdsSecretConfig(sdsSecretConfig);
if (!Strings.isNullOrEmpty(privateKey) && !Strings.isNullOrEmpty(certChain)) {
builder.addTlsCertificates(
TlsCertificate.newBuilder()
.setCertificateChain(DataSource.newBuilder().setFilename(certChain))
.setPrivateKey(DataSource.newBuilder().setFilename(privateKey))
.build());
}
return builder.build();
}
static CommonTlsContext buildCommonTlsContextFromSdsConfigForTlsCertificate(
String name, String targetUri, String trustCa) {
SdsSecretConfig sdsSecretConfig =
buildSdsSecretConfig(name, targetUri, /* channelType= */ null);
CommonTlsContext.Builder builder =
CommonTlsContext.newBuilder().addTlsCertificateSdsSecretConfigs(sdsSecretConfig);
if (!Strings.isNullOrEmpty(trustCa)) {
builder.setValidationContext(
CertificateValidationContext.newBuilder()
.setTrustedCa(DataSource.newBuilder().setFilename(trustCa))
.build());
}
return builder.build();
}
/** takes additional values and creates CombinedCertificateValidationContext as needed. */
@SuppressWarnings("deprecation")
static io.envoyproxy.envoy.api.v2.auth.CommonTlsContext
buildCommonTlsContextWithAdditionalValuesV2(
String certName,
String certTargetUri,
String validationContextName,
String validationContextTargetUri,
Iterable<String> verifySubjectAltNames,
Iterable<String> alpnNames,
String channelType) {
io.envoyproxy.envoy.api.v2.auth.CommonTlsContext.Builder builder =
io.envoyproxy.envoy.api.v2.auth.CommonTlsContext.newBuilder();
io.envoyproxy.envoy.api.v2.auth.SdsSecretConfig sdsSecretConfig =
buildSdsSecretConfigV2(certName, certTargetUri, channelType);
if (sdsSecretConfig != null) {
builder.addTlsCertificateSdsSecretConfigs(sdsSecretConfig);
}
sdsSecretConfig =
buildSdsSecretConfigV2(validationContextName, validationContextTargetUri, channelType);
io.envoyproxy.envoy.api.v2.auth.CertificateValidationContext certValidationContext =
verifySubjectAltNames == null ? null
: io.envoyproxy.envoy.api.v2.auth.CertificateValidationContext.newBuilder()
.addAllVerifySubjectAltName(verifySubjectAltNames).build();
if (sdsSecretConfig != null && certValidationContext != null) {
io.envoyproxy.envoy.api.v2.auth.CommonTlsContext.CombinedCertificateValidationContext
combined =
io.envoyproxy.envoy.api.v2.auth.CommonTlsContext.CombinedCertificateValidationContext
.newBuilder()
.setDefaultValidationContext(certValidationContext)
.setValidationContextSdsSecretConfig(sdsSecretConfig)
.build();
builder.setCombinedValidationContext(combined);
} else if (sdsSecretConfig != null) {
builder.setValidationContextSdsSecretConfig(sdsSecretConfig);
} else if (certValidationContext != null) {
builder.setValidationContext(certValidationContext);
}
if (alpnNames != null) {
builder.addAllAlpnProtocols(alpnNames);
}
return builder.build();
}
/** takes additional values and creates CombinedCertificateValidationContext as needed. */
static CommonTlsContext buildCommonTlsContextWithAdditionalValues(
String certName,
String certTargetUri,
String validationContextName,
String validationContextTargetUri,
Iterable<StringMatcher> matchSubjectAltNames,
Iterable<String> alpnNames,
String channelType) {
CommonTlsContext.Builder builder = CommonTlsContext.newBuilder();
SdsSecretConfig sdsSecretConfig = buildSdsSecretConfig(certName, certTargetUri, channelType);
if (sdsSecretConfig != null) {
builder.addTlsCertificateSdsSecretConfigs(sdsSecretConfig);
}
sdsSecretConfig =
buildSdsSecretConfig(validationContextName, validationContextTargetUri, channelType);
CertificateValidationContext certValidationContext =
matchSubjectAltNames == null
? null
: CertificateValidationContext.newBuilder()
.addAllMatchSubjectAltNames(matchSubjectAltNames)
.build();
if (sdsSecretConfig != null && certValidationContext != null) {
CombinedCertificateValidationContext.Builder combinedBuilder =
CombinedCertificateValidationContext.newBuilder()
.setDefaultValidationContext(certValidationContext)
.setValidationContextSdsSecretConfig(sdsSecretConfig);
builder.setCombinedValidationContext(combinedBuilder);
} else if (sdsSecretConfig != null) {
builder.setValidationContextSdsSecretConfig(sdsSecretConfig);
} else if (certValidationContext != null) {
builder.setValidationContext(certValidationContext);
}
if (alpnNames != null) {
builder.addAllAlpnProtocols(alpnNames);
}
return builder.build();
}
/** Helper method to build DownstreamTlsContext for multiple test classes. */
static io.envoyproxy.envoy.api.v2.auth.DownstreamTlsContext buildDownstreamTlsContextV2(
io.envoyproxy.envoy.api.v2.auth.CommonTlsContext commonTlsContext,
boolean requireClientCert) {
io.envoyproxy.envoy.api.v2.auth.DownstreamTlsContext downstreamTlsContext =
io.envoyproxy.envoy.api.v2.auth.DownstreamTlsContext.newBuilder()
.setCommonTlsContext(commonTlsContext)
.setRequireClientCertificate(BoolValue.of(requireClientCert))
.build();
return downstreamTlsContext;
}
/** Helper method to build DownstreamTlsContext for multiple test classes. */
static DownstreamTlsContext buildDownstreamTlsContext(
CommonTlsContext commonTlsContext, boolean requireClientCert) {
DownstreamTlsContext downstreamTlsContext =
DownstreamTlsContext.newBuilder()
.setCommonTlsContext(commonTlsContext)
.setRequireClientCertificate(BoolValue.of(requireClientCert))
.build();
return downstreamTlsContext;
}
/** Helper method to build internal DownstreamTlsContext for multiple test classes. */
static EnvoyServerProtoData.DownstreamTlsContext buildInternalDownstreamTlsContext(
CommonTlsContext commonTlsContext, boolean requireClientCert) {
return EnvoyServerProtoData.DownstreamTlsContext.fromEnvoyProtoDownstreamTlsContext(
buildDownstreamTlsContext(commonTlsContext, requireClientCert));
}
/** Helper method for creating DownstreamTlsContext values with names. */
public static io.envoyproxy.envoy.api.v2.auth.DownstreamTlsContext
buildTestDownstreamTlsContextV2(String certName, String validationContextName) {
return buildDownstreamTlsContextV2(
buildCommonTlsContextWithAdditionalValuesV2(
certName,
"unix:/var/run/sds/uds_path",
validationContextName,
"unix:/var/run/sds/uds_path",
Arrays.asList("spiffe://grpc-sds-testing.svc.id.goog/ns/default/sa/bob"),
Arrays.asList("managed-tls"),
null),
/* requireClientCert= */ false);
}
/** Helper method for creating DownstreamTlsContext values with names. */
public static DownstreamTlsContext buildTestDownstreamTlsContext(
String certName, String validationContextName) {
return buildDownstreamTlsContext(
buildCommonTlsContextWithAdditionalValues(
certName,
"unix:/var/run/sds/uds_path",
validationContextName,
"unix:/var/run/sds/uds_path",
Arrays.asList(
StringMatcher.newBuilder()
.setExact("spiffe://grpc-sds-testing.svc.id.goog/ns/default/sa/bob")
.build()),
Arrays.asList("managed-tls"),
null),
/* requireClientCert= */ false);
}
public static EnvoyServerProtoData.DownstreamTlsContext buildTestInternalDownstreamTlsContext(
String certName, String validationContextName) {
return EnvoyServerProtoData.DownstreamTlsContext.fromEnvoyProtoDownstreamTlsContext(
buildTestDownstreamTlsContext(certName, validationContextName));
}
static String getTempFileNameForResourcesFile(String resFile) throws IOException {
return TestUtils.loadCert(resFile).getAbsolutePath();
}
/**
* Helper method to build DownstreamTlsContext for above tests. Called from other classes as well.
*/
public static EnvoyServerProtoData.DownstreamTlsContext buildDownstreamTlsContextFromFilenames(
@Nullable String privateKey, @Nullable String certChain, @Nullable String trustCa) {
return buildDownstreamTlsContextFromFilenamesWithClientAuth(privateKey, certChain, trustCa,
false);
}
/**
* Helper method to build DownstreamTlsContext for above tests. Called from other classes as well.
*/
public static EnvoyServerProtoData.DownstreamTlsContext
buildDownstreamTlsContextFromFilenamesWithClientCertRequired(
@Nullable String privateKey, @Nullable String certChain, @Nullable String trustCa) {
return buildDownstreamTlsContextFromFilenamesWithClientAuth(privateKey, certChain, trustCa,
true);
}
private static EnvoyServerProtoData.DownstreamTlsContext
buildDownstreamTlsContextFromFilenamesWithClientAuth(
@Nullable String privateKey,
@Nullable String certChain,
@Nullable String trustCa,
boolean requireClientCert) {
// get temp file for each file
try {
if (certChain != null) {
certChain = getTempFileNameForResourcesFile(certChain);
}
if (privateKey != null) {
privateKey = getTempFileNameForResourcesFile(privateKey);
}
if (trustCa != null) {
trustCa = getTempFileNameForResourcesFile(trustCa);
}
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
return buildInternalDownstreamTlsContext(
buildCommonTlsContextFromFilenames(privateKey, certChain, trustCa), requireClientCert);
}
/**
* Helper method to build UpstreamTlsContext for above tests. Called from other classes as well.
*/
public static EnvoyServerProtoData.UpstreamTlsContext buildUpstreamTlsContextFromFilenames(
@Nullable String privateKey, @Nullable String certChain, @Nullable String trustCa) {
try {
if (certChain != null) {
certChain = getTempFileNameForResourcesFile(certChain);
}
if (privateKey != null) {
privateKey = getTempFileNameForResourcesFile(privateKey);
}
if (trustCa != null) {
trustCa = getTempFileNameForResourcesFile(trustCa);
}
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
return buildUpstreamTlsContext(
buildCommonTlsContextFromFilenames(privateKey, certChain, trustCa));
}
private static CommonTlsContext buildCommonTlsContextFromFilenames(
String privateKey, String certChain, String trustCa) {
TlsCertificate tlsCert = null;
if (!Strings.isNullOrEmpty(privateKey) && !Strings.isNullOrEmpty(certChain)) {
tlsCert =
TlsCertificate.newBuilder()
.setCertificateChain(DataSource.newBuilder().setFilename(certChain))
.setPrivateKey(DataSource.newBuilder().setFilename(privateKey))
.build();
}
CertificateValidationContext certContext = null;
if (!Strings.isNullOrEmpty(trustCa)) {
certContext =
CertificateValidationContext.newBuilder()
.setTrustedCa(DataSource.newBuilder().setFilename(trustCa))
.build();
}
return getCommonTlsContext(tlsCert, certContext);
}
static CommonTlsContext getCommonTlsContext(
TlsCertificate tlsCertificate, CertificateValidationContext certContext) {
CommonTlsContext.Builder builder = CommonTlsContext.newBuilder();
if (tlsCertificate != null) {
builder = builder.addTlsCertificates(tlsCertificate);
}
if (certContext != null) {
builder = builder.setValidationContext(certContext);
}
return builder.build();
}
/**
* Helper method to build UpstreamTlsContext for above tests. Called from other classes as well.
*/
static EnvoyServerProtoData.UpstreamTlsContext buildUpstreamTlsContext(
CommonTlsContext commonTlsContext) {
UpstreamTlsContext upstreamTlsContext =
UpstreamTlsContext.newBuilder().setCommonTlsContext(commonTlsContext).build();
return EnvoyServerProtoData.UpstreamTlsContext.fromEnvoyProtoUpstreamTlsContext(
upstreamTlsContext);
}
/** Gets a cert from contents of a resource. */
public static X509Certificate getCertFromResourceName(String resourceName)
throws IOException, CertificateException {
try (ByteArrayInputStream bais =
new ByteArrayInputStream(getResourceContents(resourceName).getBytes(UTF_8))) {
return CertificateUtils.toX509Certificate(bais);
}
}
/** Gets contents of a resource from TestUtils.class loader. */
public static String getResourceContents(String resourceName) throws IOException {
InputStream inputStream = TestUtils.class.getResourceAsStream("/certs/" + resourceName);
String text = null;
try (Reader reader = new InputStreamReader(inputStream, UTF_8)) {
text = CharStreams.toString(reader);
}
return text;
}
}