Skip to content

Commit a1cbaad

Browse files
committed
Bootstrap file validation for TLS channel creds
1 parent 3767117 commit a1cbaad

File tree

5 files changed

+192
-3
lines changed

5 files changed

+192
-3
lines changed

api/src/main/java/io/grpc/TlsChannelCredentials.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Collections;
2727
import java.util.EnumSet;
2828
import java.util.List;
29+
import java.util.Map;
2930
import java.util.Set;
3031
import javax.net.ssl.KeyManager;
3132
import javax.net.ssl.TrustManager;
@@ -49,6 +50,7 @@ public static ChannelCredentials create() {
4950
private final List<KeyManager> keyManagers;
5051
private final byte[] rootCertificates;
5152
private final List<TrustManager> trustManagers;
53+
private final Map<String, ?> customCertificatesConfig;
5254

5355
TlsChannelCredentials(Builder builder) {
5456
fakeFeature = builder.fakeFeature;
@@ -58,6 +60,7 @@ public static ChannelCredentials create() {
5860
keyManagers = builder.keyManagers;
5961
rootCertificates = builder.rootCertificates;
6062
trustManagers = builder.trustManagers;
63+
customCertificatesConfig = builder.customCertificatesConfig;
6164
}
6265

6366
/**
@@ -121,6 +124,21 @@ public List<TrustManager> getTrustManagers() {
121124
return trustManagers;
122125
}
123126

127+
/**
128+
* Returns custom certificates config. It contains following entries:
129+
*
130+
* <ul>
131+
* <li>{@code "ca_certificate_file"} key containing the path to the root certificate file</li>
132+
* <li>{@code "certificate_file"} key containing the path to the identity certificate file</li>
133+
* <li>{@code "private_key_file"} key containing the path to the private key</li>
134+
* <li>{@code "refresh_interval"} key specifying the frequency of updates to the above
135+
* files</li>
136+
* </ul>
137+
*/
138+
public Map<String, ?> getCustomCertificatesConfig() {
139+
return customCertificatesConfig;
140+
}
141+
124142
/**
125143
* Returns an empty set if this credential can be adequately understood via
126144
* the features listed, otherwise returns a hint of features that are lacking
@@ -228,6 +246,7 @@ public static final class Builder {
228246
private List<KeyManager> keyManagers;
229247
private byte[] rootCertificates;
230248
private List<TrustManager> trustManagers;
249+
private Map<String, ?> customCertificatesConfig;
231250

232251
private Builder() {}
233252

@@ -355,6 +374,11 @@ public Builder trustManager(TrustManager... trustManagers) {
355374
return this;
356375
}
357376

377+
public Builder customCertificatesConfig(Map<String, ?> customCertificatesConfig) {
378+
this.customCertificatesConfig = customCertificatesConfig;
379+
return this;
380+
}
381+
358382
private void clearTrustManagers() {
359383
this.rootCertificates = null;
360384
this.trustManagers = null;

xds/src/main/java/io/grpc/xds/client/BootstrapperImpl.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.google.common.collect.ImmutableMap;
2222
import io.grpc.Internal;
2323
import io.grpc.InternalLogId;
24+
import io.grpc.TlsChannelCredentials;
2425
import io.grpc.internal.GrpcUtil;
2526
import io.grpc.internal.GrpcUtil.GrpcBuildVersion;
2627
import io.grpc.internal.JsonParser;
@@ -170,9 +171,9 @@ protected BootstrapInfo.Builder bootstrapBuilder(Map<String, ?> rawData)
170171
builder.node(nodeBuilder.build());
171172

172173
Map<String, ?> certProvidersBlob = JsonUtil.getObject(rawData, "certificate_providers");
174+
Map<String, CertificateProviderInfo> certProviders = new HashMap<>();
173175
if (certProvidersBlob != null) {
174176
logger.log(XdsLogLevel.INFO, "Configured with {0} cert providers", certProvidersBlob.size());
175-
Map<String, CertificateProviderInfo> certProviders = new HashMap<>(certProvidersBlob.size());
176177
for (String name : certProvidersBlob.keySet()) {
177178
Map<String, ?> valueMap = JsonUtil.getObject(certProvidersBlob, name);
178179
String pluginName =
@@ -183,6 +184,21 @@ protected BootstrapInfo.Builder bootstrapBuilder(Map<String, ?> rawData)
183184
CertificateProviderInfo.create(pluginName, config);
184185
certProviders.put(name, certificateProviderInfo);
185186
}
187+
}
188+
189+
for (ServerInfo serverInfo : servers) {
190+
Object creds = serverInfo.implSpecificConfig();
191+
if (creds instanceof TlsChannelCredentials) {
192+
Map<String, ?> config = ((TlsChannelCredentials)creds).getCustomCertificatesConfig();
193+
if (config != null) {
194+
CertificateProviderInfo certificateProviderInfo =
195+
CertificateProviderInfo.create("file_watcher", config);
196+
certProviders.put("mtls_channel_creds_identity_certs", certificateProviderInfo);
197+
}
198+
}
199+
}
200+
201+
if (!certProviders.isEmpty()) {
186202
builder.certProviders(certProviders);
187203
}
188204

xds/src/main/java/io/grpc/xds/internal/TlsXdsCredentialsProvider.java

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818

1919
import io.grpc.ChannelCredentials;
2020
import io.grpc.TlsChannelCredentials;
21+
import io.grpc.internal.JsonUtil;
2122
import io.grpc.xds.XdsCredentialsProvider;
23+
import java.io.File;
24+
import java.io.IOException;
2225
import java.util.Map;
2326

2427
/**
@@ -30,7 +33,42 @@ public final class TlsXdsCredentialsProvider extends XdsCredentialsProvider {
3033

3134
@Override
3235
protected ChannelCredentials newChannelCredentials(Map<String, ?> jsonConfig) {
33-
return TlsChannelCredentials.create();
36+
TlsChannelCredentials.Builder builder = TlsChannelCredentials.newBuilder();
37+
38+
if (jsonConfig == null) {
39+
return builder.build();
40+
}
41+
42+
// use trust certificate file path from bootstrap config if provided; else use system default
43+
String rootCertPath = JsonUtil.getString(jsonConfig, "ca_certificate_file");
44+
if (rootCertPath != null) {
45+
try {
46+
builder.trustManager(new File(rootCertPath));
47+
} catch (IOException e) {
48+
return null;
49+
}
50+
}
51+
52+
// use certificate chain and private key file paths from bootstrap config if provided. Mind that
53+
// both JSON values must be either set (mTLS case) or both unset (TLS case)
54+
String certChainPath = JsonUtil.getString(jsonConfig, "certificate_file");
55+
String privateKeyPath = JsonUtil.getString(jsonConfig, "private_key_file");
56+
if (certChainPath != null && privateKeyPath != null) {
57+
try {
58+
builder.keyManager(new File(certChainPath), new File(privateKeyPath));
59+
} catch (IOException e) {
60+
return null;
61+
}
62+
} else if (certChainPath != null || privateKeyPath != null) {
63+
return null;
64+
}
65+
66+
// save json config when custom certificate paths were provided in a bootstrap
67+
if (rootCertPath != null || certChainPath != null) {
68+
builder.customCertificatesConfig(jsonConfig);
69+
}
70+
71+
return builder.build();
3472
}
3573

3674
@Override

xds/src/test/java/io/grpc/xds/GrpcBootstrapperImplTest.java

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
package io.grpc.xds;
1818

1919
import static com.google.common.truth.Truth.assertThat;
20+
import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CA_PEM_FILE;
21+
import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_KEY_FILE;
22+
import static io.grpc.xds.internal.security.CommonTlsContextTestsUtil.CLIENT_PEM_FILE;
2023
import static org.junit.Assert.assertThrows;
2124
import static org.junit.Assert.fail;
2225
import static org.mockito.Mockito.mock;
@@ -26,9 +29,11 @@
2629
import com.google.common.collect.Iterables;
2730
import io.grpc.internal.GrpcUtil;
2831
import io.grpc.internal.GrpcUtil.GrpcBuildVersion;
32+
import io.grpc.internal.testing.TestUtils;
2933
import io.grpc.xds.client.Bootstrapper;
3034
import io.grpc.xds.client.Bootstrapper.AuthorityInfo;
3135
import io.grpc.xds.client.Bootstrapper.BootstrapInfo;
36+
import io.grpc.xds.client.Bootstrapper.CertificateProviderInfo;
3237
import io.grpc.xds.client.Bootstrapper.ServerInfo;
3338
import io.grpc.xds.client.BootstrapperImpl;
3439
import io.grpc.xds.client.CommonBootstrapperTestUtils;
@@ -896,6 +901,60 @@ public void badFederationConfig() {
896901
}
897902
}
898903

904+
@Test
905+
public void parseTlsChannelCredentialsWithCustomCertificatesConfig()
906+
throws XdsInitializationException, IOException {
907+
String rootCertPath = TestUtils.loadCert(CA_PEM_FILE).getAbsolutePath();
908+
String certChainPath = TestUtils.loadCert(CLIENT_PEM_FILE).getAbsolutePath();
909+
String privateKeyPath = TestUtils.loadCert(CLIENT_KEY_FILE).getAbsolutePath();
910+
911+
String rawData = "{\n"
912+
+ " \"xds_servers\": [\n"
913+
+ " {\n"
914+
+ " \"server_uri\": \"" + SERVER_URI + "\",\n"
915+
+ " \"channel_creds\": [\n"
916+
+ " {\n"
917+
+ " \"type\": \"tls\","
918+
+ " \"config\": {\n"
919+
+ " \"ca_certificate_file\": \"" + rootCertPath + "\",\n"
920+
+ " \"certificate_file\": \"" + certChainPath + "\",\n"
921+
+ " \"private_key_file\": \"" + privateKeyPath + "\"\n"
922+
+ " }\n"
923+
+ " }\n"
924+
+ " ]\n"
925+
+ " }\n"
926+
+ " ]\n"
927+
+ "}";
928+
929+
bootstrapper.setFileReader(createFileReader(BOOTSTRAP_FILE_PATH, rawData));
930+
BootstrapInfo info = bootstrapper.bootstrap();
931+
assertThat(info.servers()).hasSize(1);
932+
ServerInfo serverInfo = Iterables.getOnlyElement(info.servers());
933+
assertThat(serverInfo.target()).isEqualTo(SERVER_URI);
934+
assertThat(serverInfo.implSpecificConfig()).isEqualTo(
935+
ImmutableMap.of(
936+
"type", "tls",
937+
"config", ImmutableMap.of(
938+
"ca_certificate_file", rootCertPath,
939+
"certificate_file", certChainPath,
940+
"private_key_file", privateKeyPath)));
941+
assertThat(info.node()).isEqualTo(getNodeBuilder().build());
942+
943+
assertThat(info.certProviders()).hasSize(1);
944+
ImmutableMap<String, CertificateProviderInfo> certProviderInfo = info.certProviders();
945+
assertThat(certProviderInfo.keySet()).containsExactly("mtls_channel_creds_identity_certs");
946+
CertificateProviderInfo mtlsChannelCredCertProviderInfo =
947+
certProviderInfo.get("mtls_channel_creds_identity_certs");
948+
assertThat(mtlsChannelCredCertProviderInfo.config().keySet())
949+
.containsExactly("ca_certificate_file", "certificate_file", "private_key_file");
950+
assertThat(mtlsChannelCredCertProviderInfo.config().get("ca_certificate_file"))
951+
.isEqualTo(rootCertPath);
952+
assertThat(mtlsChannelCredCertProviderInfo.config().get("certificate_file"))
953+
.isEqualTo(certChainPath);
954+
assertThat(mtlsChannelCredCertProviderInfo.config().get("private_key_file"))
955+
.isEqualTo(privateKeyPath);
956+
}
957+
899958
private static BootstrapperImpl.FileReader createFileReader(
900959
final String expectedPath, final String rawData) {
901960
return new BootstrapperImpl.FileReader() {

xds/src/test/java/io/grpc/xds/internal/TlsXdsCredentialsProviderTest.java

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,21 @@
1616

1717
package io.grpc.xds.internal;
1818

19+
import static org.junit.Assert.assertNull;
1920
import static org.junit.Assert.assertSame;
2021
import static org.junit.Assert.assertTrue;
2122
import static org.junit.Assert.fail;
2223

24+
import com.google.common.collect.ImmutableMap;
25+
import io.grpc.ChannelCredentials;
2326
import io.grpc.InternalServiceProviders;
2427
import io.grpc.TlsChannelCredentials;
2528
import io.grpc.xds.XdsCredentialsProvider;
29+
import java.io.File;
30+
import java.util.Map;
31+
import org.junit.Rule;
2632
import org.junit.Test;
33+
import org.junit.rules.TemporaryFolder;
2734
import org.junit.runner.RunWith;
2835
import org.junit.runners.JUnit4;
2936

@@ -32,6 +39,9 @@
3239
public class TlsXdsCredentialsProviderTest {
3340
private TlsXdsCredentialsProvider provider = new TlsXdsCredentialsProvider();
3441

42+
@Rule
43+
public TemporaryFolder tempFolder = new TemporaryFolder();
44+
3545
@Test
3646
public void provided() {
3747
for (XdsCredentialsProvider current
@@ -50,8 +60,50 @@ public void isAvailable() {
5060
}
5161

5262
@Test
53-
public void channelCredentials() {
63+
public void channelCredentialsWhenNullConfig() {
5464
assertSame(TlsChannelCredentials.class,
5565
provider.newChannelCredentials(null).getClass());
5666
}
67+
68+
@Test
69+
public void channelCredentialsWhenNotExistingTrustFileConfig() {
70+
Map<String, ?> jsonConfig = ImmutableMap.of(
71+
"ca_certificate_file", "/tmp/not-exisiting-file.txt");
72+
assertNull(provider.newChannelCredentials(jsonConfig));
73+
}
74+
75+
@Test
76+
public void channelCredentialsWhenNotExistingCertificateFileConfig() {
77+
Map<String, ?> jsonConfig = ImmutableMap.of(
78+
"certificate_file", "/tmp/not-exisiting-file.txt",
79+
"private_key_file", "/tmp/not-exisiting-file-2.txt");
80+
assertNull(provider.newChannelCredentials(jsonConfig));
81+
}
82+
83+
@Test
84+
public void channelCredentialsWhenInvalidConfig() throws Exception {
85+
File certFile = tempFolder.newFile(new String("identity.cert"));
86+
Map<String, ?> jsonConfig = ImmutableMap.of("certificate_file", certFile.toString());
87+
assertNull(provider.newChannelCredentials(jsonConfig));
88+
}
89+
90+
@Test
91+
public void channelCredentialsWhenValidConfig() throws Exception {
92+
File trustFile = tempFolder.newFile(new String("root.cert"));
93+
File certFile = tempFolder.newFile(new String("identity.cert"));
94+
File keyFile = tempFolder.newFile(new String("private.key"));
95+
96+
Map<String, ?> jsonConfig = ImmutableMap.of(
97+
"ca_certificate_file", trustFile.toString(),
98+
"certificate_file", certFile.toString(),
99+
"private_key_file", keyFile.toString());
100+
101+
ChannelCredentials creds = provider.newChannelCredentials(jsonConfig);
102+
assertSame(TlsChannelCredentials.class, creds.getClass());
103+
assertSame(((TlsChannelCredentials) creds).getCustomCertificatesConfig(), jsonConfig);
104+
105+
trustFile.delete();
106+
certFile.delete();
107+
keyFile.delete();
108+
}
57109
}

0 commit comments

Comments
 (0)