xds: add support for cert-providers to bootstrap file (#7285)

This commit is contained in:
sanjaypujare 2020-08-04 22:32:26 -07:00 committed by GitHub
parent afcce8d3c0
commit 34513d7ed8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 263 additions and 5 deletions

View File

@ -16,6 +16,8 @@
package io.grpc.xds;
import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.annotations.VisibleForTesting;
import io.grpc.Internal;
import io.grpc.internal.GrpcUtil;
@ -31,6 +33,7 @@ import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@ -160,7 +163,28 @@ public abstract class Bootstrapper {
nodeBuilder.setUserAgentVersion(buildVersion.getImplementationVersion());
nodeBuilder.addClientFeatures(CLIENT_FEATURE_DISABLE_OVERPROVISIONING);
return new BootstrapInfo(servers, nodeBuilder.build());
Map<String, ?> certProvidersBlob = JsonUtil.getObject(rawBootstrap, "certificate_providers");
Map<String, CertificateProviderInfo> certProviders = null;
if (certProvidersBlob != null) {
certProviders = new HashMap<>(certProvidersBlob.size());
for (String name : certProvidersBlob.keySet()) {
Map<String, ?> valueMap = JsonUtil.getObject(certProvidersBlob, name);
String pluginName =
checkForNull(JsonUtil.getString(valueMap, "plugin_name"), "plugin_name");
Map<String, ?> config = checkForNull(JsonUtil.getObject(valueMap, "config"), "config");
CertificateProviderInfo certificateProviderInfo =
new CertificateProviderInfo(pluginName, config);
certProviders.put(name, certificateProviderInfo);
}
}
return new BootstrapInfo(servers, nodeBuilder.build(), certProviders);
}
static <T> T checkForNull(T value, String fieldName) throws IOException {
if (value == null) {
throw new IOException("Invalid bootstrap: '" + fieldName + "' does not exist.");
}
return value;
}
/**
@ -225,6 +249,30 @@ public abstract class Bootstrapper {
}
}
/**
* Data class containing Certificate provider information: the plugin-name and an opaque
* Map that represents the config for that plugin.
*/
@Internal
@Immutable
public static class CertificateProviderInfo {
private final String pluginName;
private final Map<String, ?> config;
CertificateProviderInfo(String pluginName, Map<String, ?> config) {
this.pluginName = checkNotNull(pluginName, "pluginName");
this.config = checkNotNull(config, "config");
}
String getPluginName() {
return pluginName;
}
Map<String, ?> getConfig() {
return config;
}
}
/**
* Data class containing the results of reading bootstrap.
*/
@ -233,11 +281,14 @@ public abstract class Bootstrapper {
public static class BootstrapInfo {
private List<ServerInfo> servers;
private final Node node;
@Nullable private final Map<String, CertificateProviderInfo> certProviders;
@VisibleForTesting
BootstrapInfo(List<ServerInfo> servers, Node node) {
BootstrapInfo(
List<ServerInfo> servers, Node node, Map<String, CertificateProviderInfo> certProviders) {
this.servers = servers;
this.node = node;
this.certProviders = certProviders;
}
/**
@ -253,5 +304,10 @@ public abstract class Bootstrapper {
public Node getNode() {
return node;
}
/** Returns the cert-providers config map. */
public Map<String, CertificateProviderInfo> getCertProviders() {
return Collections.unmodifiableMap(certProviders);
}
}
}

View File

@ -17,6 +17,7 @@
package io.grpc.xds;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
@ -28,6 +29,7 @@ import io.grpc.xds.EnvoyProtoData.Locality;
import io.grpc.xds.EnvoyProtoData.Node;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@ -298,6 +300,206 @@ public class BootstrapperTest {
Bootstrapper.parseConfig(rawData);
}
@Test
public void parseBootstrap_validData_certProviderInstances() throws IOException {
String rawData =
"{\n"
+ " \"xds_servers\": [],\n"
+ " \"certificate_providers\": {\n"
+ " \"gcp_id\": {\n"
+ " \"plugin_name\": \"meshca\",\n"
+ " \"config\": {\n"
+ " \"server\": {\n"
+ " \"api_type\": \"GRPC\",\n"
+ " \"grpc_services\": [{\n"
+ " \"google_grpc\": {\n"
+ " \"target_uri\": \"meshca.com\",\n"
+ " \"channel_credentials\": {\"google_default\": {}},\n"
+ " \"call_credentials\": [{\n"
+ " \"sts_service\": {\n"
+ " \"token_exchange_service\": \"securetoken.googleapis.com\",\n"
+ " \"subject_token_path\": \"/etc/secret/sajwt.token\"\n"
+ " }\n"
+ " }]\n" // end call_credentials
+ " },\n" // end google_grpc
+ " \"time_out\": {\"seconds\": 10}\n"
+ " }]\n" // end grpc_services
+ " },\n" // end server
+ " \"certificate_lifetime\": {\"seconds\": 86400},\n"
+ " \"renewal_grace_period\": {\"seconds\": 3600},\n"
+ " \"key_type\": \"RSA\",\n"
+ " \"key_size\": 2048,\n"
+ " \"location\": \"https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3\"\n"
+ " }\n" // end config
+ " },\n" // end gcp_id
+ " \"file_provider\": {\n"
+ " \"plugin_name\": \"file_watcher\",\n"
+ " \"config\": {\"path\": \"/etc/secret/certs\"}\n"
+ " }\n"
+ " }\n"
+ "}";
BootstrapInfo info = Bootstrapper.parseConfig(rawData);
assertThat(info.getServers()).isEmpty();
assertThat(info.getNode()).isEqualTo(getNodeBuilder().build());
Map<String, Bootstrapper.CertificateProviderInfo> certProviders = info.getCertProviders();
assertThat(certProviders).isNotNull();
Bootstrapper.CertificateProviderInfo gcpId = certProviders.get("gcp_id");
Bootstrapper.CertificateProviderInfo fileProvider = certProviders.get("file_provider");
assertThat(gcpId.getPluginName()).isEqualTo("meshca");
assertThat(gcpId.getConfig()).isInstanceOf(Map.class);
assertThat(fileProvider.getPluginName()).isEqualTo("file_watcher");
assertThat(fileProvider.getConfig()).isInstanceOf(Map.class);
Map<String, ?> meshCaConfig = (Map<String, ?>)gcpId.getConfig();
assertThat(meshCaConfig.get("key_size")).isEqualTo(2048);
}
@Test
public void parseBootstrap_badPluginName() throws IOException {
String rawData =
"{\n"
+ " \"xds_servers\": [],\n"
+ " \"certificate_providers\": {\n"
+ " \"gcp_id\": {\n"
+ " \"plugin_name\": 234,\n"
+ " \"config\": {\n"
+ " \"server\": {\n"
+ " \"api_type\": \"GRPC\",\n"
+ " \"grpc_services\": [{\n"
+ " \"google_grpc\": {\n"
+ " \"target_uri\": \"meshca.com\",\n"
+ " \"channel_credentials\": {\"google_default\": {}},\n"
+ " \"call_credentials\": [{\n"
+ " \"sts_service\": {\n"
+ " \"token_exchange_service\": \"securetoken.googleapis.com\",\n"
+ " \"subject_token_path\": \"/etc/secret/sajwt.token\"\n"
+ " }\n"
+ " }]\n" // end call_credentials
+ " },\n" // end google_grpc
+ " \"time_out\": {\"seconds\": 10}\n"
+ " }]\n" // end grpc_services
+ " },\n" // end server
+ " \"certificate_lifetime\": {\"seconds\": 86400},\n"
+ " \"renewal_grace_period\": {\"seconds\": 3600},\n"
+ " \"key_type\": \"RSA\",\n"
+ " \"key_size\": 2048,\n"
+ " \"location\": \"https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3\"\n"
+ " }\n" // end config
+ " },\n" // end gcp_id
+ " \"file_provider\": {\n"
+ " \"plugin_name\": \"file_watcher\",\n"
+ " \"config\": {\"path\": \"/etc/secret/certs\"}\n"
+ " }\n"
+ " }\n"
+ "}";
try {
Bootstrapper.parseConfig(rawData);
fail("exception expected");
} catch (ClassCastException expected) {
assertThat(expected).hasMessageThat().contains("value '234.0' for key 'plugin_name' in");
}
}
@Test
public void parseBootstrap_badConfig() throws IOException {
String rawData =
"{\n"
+ " \"xds_servers\": [],\n"
+ " \"certificate_providers\": {\n"
+ " \"gcp_id\": {\n"
+ " \"plugin_name\": \"meshca\",\n"
+ " \"config\": \"badValue\"\n"
+ " },\n" // end gcp_id
+ " \"file_provider\": {\n"
+ " \"plugin_name\": \"file_watcher\",\n"
+ " \"config\": {\"path\": \"/etc/secret/certs\"}\n"
+ " }\n"
+ " }\n"
+ "}";
try {
Bootstrapper.parseConfig(rawData);
fail("exception expected");
} catch (ClassCastException expected) {
assertThat(expected).hasMessageThat().contains("value 'badValue' for key 'config' in");
}
}
@Test
public void parseBootstrap_missingConfig() throws IOException {
String rawData =
"{\n"
+ " \"xds_servers\": [],\n"
+ " \"certificate_providers\": {\n"
+ " \"gcp_id\": {\n"
+ " \"plugin_name\": \"meshca\"\n"
+ " },\n" // end gcp_id
+ " \"file_provider\": {\n"
+ " \"plugin_name\": \"file_watcher\",\n"
+ " \"config\": {\"path\": \"/etc/secret/certs\"}\n"
+ " }\n"
+ " }\n"
+ "}";
try {
Bootstrapper.parseConfig(rawData);
fail("exception expected");
} catch (IOException expected) {
assertThat(expected)
.hasMessageThat()
.isEqualTo("Invalid bootstrap: 'config' does not exist.");
}
}
@Test
public void parseBootstrap_missingPluginName() throws IOException {
String rawData =
"{\n"
+ " \"xds_servers\": [],\n"
+ " \"certificate_providers\": {\n"
+ " \"gcp_id\": {\n"
+ " \"plugin_name\": \"meshca\",\n"
+ " \"config\": {\n"
+ " \"server\": {\n"
+ " \"api_type\": \"GRPC\",\n"
+ " \"grpc_services\": [{\n"
+ " \"google_grpc\": {\n"
+ " \"target_uri\": \"meshca.com\",\n"
+ " \"channel_credentials\": {\"google_default\": {}},\n"
+ " \"call_credentials\": [{\n"
+ " \"sts_service\": {\n"
+ " \"token_exchange_service\": \"securetoken.googleapis.com\",\n"
+ " \"subject_token_path\": \"/etc/secret/sajwt.token\"\n"
+ " }\n"
+ " }]\n" // end call_credentials
+ " },\n" // end google_grpc
+ " \"time_out\": {\"seconds\": 10}\n"
+ " }]\n" // end grpc_services
+ " },\n" // end server
+ " \"certificate_lifetime\": {\"seconds\": 86400},\n"
+ " \"renewal_grace_period\": {\"seconds\": 3600},\n"
+ " \"key_type\": \"RSA\",\n"
+ " \"key_size\": 2048,\n"
+ " \"location\": \"https://container.googleapis.com/v1/project/test-project1/locations/test-zone2/clusters/test-cluster3\"\n"
+ " }\n" // end config
+ " },\n" // end gcp_id
+ " \"file_provider\": {\n"
+ " \"config\": {\"path\": \"/etc/secret/certs\"}\n"
+ " }\n"
+ " }\n"
+ "}";
try {
Bootstrapper.parseConfig(rawData);
fail("exception expected");
} catch (IOException expected) {
assertThat(expected)
.hasMessageThat()
.isEqualTo("Invalid bootstrap: 'plugin_name' does not exist.");
}
}
private static Node.Builder getNodeBuilder() {
GrpcBuildVersion buildVersion = GrpcUtil.getGrpcBuildVersion();
return

View File

@ -232,7 +232,7 @@ public class EdsLoadBalancerTest {
final List<ServerInfo> serverList = ImmutableList.of(
new ServerInfo("trafficdirector.googleapis.com", ImmutableList.<ChannelCreds>of(), null));
Node node = Node.newBuilder().build();
BootstrapInfo bootstrapInfo = new BootstrapInfo(serverList, node);
BootstrapInfo bootstrapInfo = new BootstrapInfo(serverList, node, null);
doReturn(bootstrapInfo).when(bootstrapper).readBootstrap();
if (isFullFlow) {

View File

@ -170,7 +170,7 @@ public class XdsNameResolverIntegrationTest {
public BootstrapInfo readBootstrap() {
List<ServerInfo> serverList =
ImmutableList.of(new ServerInfo(serverName, ImmutableList.<ChannelCreds>of(), null));
return new BootstrapInfo(serverList, FAKE_BOOTSTRAP_NODE);
return new BootstrapInfo(serverList, FAKE_BOOTSTRAP_NODE, null);
}
};
xdsNameResolver =
@ -194,7 +194,7 @@ public class XdsNameResolverIntegrationTest {
Bootstrapper bootstrapper = new Bootstrapper() {
@Override
public BootstrapInfo readBootstrap() {
return new BootstrapInfo(ImmutableList.<ServerInfo>of(), FAKE_BOOTSTRAP_NODE);
return new BootstrapInfo(ImmutableList.<ServerInfo>of(), FAKE_BOOTSTRAP_NODE, null);
}
};