mirror of https://github.com/grpc/grpc-java.git
xds: implement ZatarCertificateProviderProvider (#7526)
This commit is contained in:
parent
0b6f29371b
commit
0e7cd05bf4
|
|
@ -177,4 +177,47 @@ final class ZatarCertificateProvider extends CertificateProvider {
|
||||||
checkAndReloadCertificates();
|
checkAndReloadCertificates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract static class Factory {
|
||||||
|
private static final Factory DEFAULT_INSTANCE =
|
||||||
|
new Factory() {
|
||||||
|
@Override
|
||||||
|
ZatarCertificateProvider create(
|
||||||
|
DistributorWatcher watcher,
|
||||||
|
boolean notifyCertUpdates,
|
||||||
|
String directory,
|
||||||
|
String certFile,
|
||||||
|
String privateKeyFile,
|
||||||
|
String trustFile,
|
||||||
|
long refreshIntervalInSeconds,
|
||||||
|
ScheduledExecutorService scheduledExecutorService,
|
||||||
|
TimeProvider timeProvider) {
|
||||||
|
return new ZatarCertificateProvider(
|
||||||
|
watcher,
|
||||||
|
notifyCertUpdates,
|
||||||
|
directory,
|
||||||
|
certFile,
|
||||||
|
privateKeyFile,
|
||||||
|
trustFile,
|
||||||
|
refreshIntervalInSeconds,
|
||||||
|
scheduledExecutorService,
|
||||||
|
timeProvider);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static Factory getInstance() {
|
||||||
|
return DEFAULT_INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract ZatarCertificateProvider create(
|
||||||
|
DistributorWatcher watcher,
|
||||||
|
boolean notifyCertUpdates,
|
||||||
|
String directory,
|
||||||
|
String certFile,
|
||||||
|
String privateKeyFile,
|
||||||
|
String trustFile,
|
||||||
|
long refreshIntervalInSeconds,
|
||||||
|
ScheduledExecutorService scheduledExecutorService,
|
||||||
|
TimeProvider timeProvider);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
/*
|
||||||
|
* 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.certprovider;
|
||||||
|
|
||||||
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
import io.grpc.internal.JsonUtil;
|
||||||
|
import io.grpc.internal.TimeProvider;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provider of {@link ZatarCertificateProvider}s.
|
||||||
|
*/
|
||||||
|
final class ZatarCertificateProviderProvider implements CertificateProviderProvider {
|
||||||
|
|
||||||
|
private static final String DIRECTORY_KEY = "directory";
|
||||||
|
private static final String CERT_FILE_KEY = "certificate-file";
|
||||||
|
private static final String KEY_FILE_KEY = "private-key-file";
|
||||||
|
private static final String ROOT_FILE_KEY = "ca-certificate-file";
|
||||||
|
private static final String REFRESH_INTERVAL_KEY = "refresh-interval";
|
||||||
|
|
||||||
|
@VisibleForTesting static final long REFRESH_INTERVAL_DEFAULT = 600L;
|
||||||
|
|
||||||
|
|
||||||
|
static final String ZATAR_PROVIDER_NAME = "gke-cas-certs";
|
||||||
|
|
||||||
|
static {
|
||||||
|
CertificateProviderRegistry.getInstance()
|
||||||
|
.register(
|
||||||
|
new ZatarCertificateProviderProvider(
|
||||||
|
ZatarCertificateProvider.Factory.getInstance(),
|
||||||
|
ScheduledExecutorServiceFactory.DEFAULT_INSTANCE,
|
||||||
|
TimeProvider.SYSTEM_TIME_PROVIDER));
|
||||||
|
}
|
||||||
|
|
||||||
|
final ZatarCertificateProvider.Factory zatarCertificateProviderFactory;
|
||||||
|
private final ScheduledExecutorServiceFactory scheduledExecutorServiceFactory;
|
||||||
|
private final TimeProvider timeProvider;
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
ZatarCertificateProviderProvider(
|
||||||
|
ZatarCertificateProvider.Factory zatarCertificateProviderFactory,
|
||||||
|
ScheduledExecutorServiceFactory scheduledExecutorServiceFactory,
|
||||||
|
TimeProvider timeProvider) {
|
||||||
|
this.zatarCertificateProviderFactory = zatarCertificateProviderFactory;
|
||||||
|
this.scheduledExecutorServiceFactory = scheduledExecutorServiceFactory;
|
||||||
|
this.timeProvider = timeProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return ZATAR_PROVIDER_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CertificateProvider createCertificateProvider(
|
||||||
|
Object config, CertificateProvider.DistributorWatcher watcher, boolean notifyCertUpdates) {
|
||||||
|
|
||||||
|
Config configObj = validateAndTranslateConfig(config);
|
||||||
|
return zatarCertificateProviderFactory.create(
|
||||||
|
watcher,
|
||||||
|
notifyCertUpdates,
|
||||||
|
configObj.directory,
|
||||||
|
configObj.certFile,
|
||||||
|
configObj.keyFile,
|
||||||
|
configObj.rootFile,
|
||||||
|
configObj.refrehInterval,
|
||||||
|
scheduledExecutorServiceFactory.create(),
|
||||||
|
timeProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String checkForNullAndGet(Map<String, ?> map, String key) {
|
||||||
|
return checkNotNull(JsonUtil.getString(map, key), "'" + key + "' is required in the config");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Config validateAndTranslateConfig(Object config) {
|
||||||
|
checkArgument(config instanceof Map, "Only Map supported for config");
|
||||||
|
@SuppressWarnings("unchecked") Map<String, ?> map = (Map<String, ?>)config;
|
||||||
|
|
||||||
|
Config configObj = new Config();
|
||||||
|
configObj.directory = checkForNullAndGet(map, DIRECTORY_KEY);
|
||||||
|
configObj.certFile = checkForNullAndGet(map, CERT_FILE_KEY);
|
||||||
|
configObj.keyFile = checkForNullAndGet(map, KEY_FILE_KEY);
|
||||||
|
configObj.rootFile = checkForNullAndGet(map, ROOT_FILE_KEY);
|
||||||
|
configObj.refrehInterval = JsonUtil.getNumberAsLong(map, REFRESH_INTERVAL_KEY);
|
||||||
|
if (configObj.refrehInterval == null) {
|
||||||
|
configObj.refrehInterval = REFRESH_INTERVAL_DEFAULT;
|
||||||
|
}
|
||||||
|
return configObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract static class ScheduledExecutorServiceFactory {
|
||||||
|
|
||||||
|
private static final ScheduledExecutorServiceFactory DEFAULT_INSTANCE =
|
||||||
|
new ScheduledExecutorServiceFactory() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
ScheduledExecutorService create() {
|
||||||
|
return Executors.newSingleThreadScheduledExecutor(
|
||||||
|
new ThreadFactoryBuilder()
|
||||||
|
.setNameFormat("zatar" + "-%d")
|
||||||
|
.setDaemon(true)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
abstract ScheduledExecutorService create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** POJO class for storing various config values. */
|
||||||
|
@VisibleForTesting
|
||||||
|
static class Config {
|
||||||
|
String directory;
|
||||||
|
String certFile;
|
||||||
|
String keyFile;
|
||||||
|
String rootFile;
|
||||||
|
Long refrehInterval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,214 @@
|
||||||
|
/*
|
||||||
|
* 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.certprovider;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import io.grpc.internal.JsonParser;
|
||||||
|
import io.grpc.internal.TimeProvider;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.MockitoAnnotations;
|
||||||
|
|
||||||
|
/** Unit tests for {@link ZatarCertificateProviderProvider}. */
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class ZatarCertificateProviderProviderTest {
|
||||||
|
|
||||||
|
@Mock ZatarCertificateProvider.Factory zatarCertificateProviderFactory;
|
||||||
|
@Mock private ZatarCertificateProviderProvider.ScheduledExecutorServiceFactory
|
||||||
|
scheduledExecutorServiceFactory;
|
||||||
|
@Mock private TimeProvider timeProvider;
|
||||||
|
|
||||||
|
private ZatarCertificateProviderProvider provider;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws IOException {
|
||||||
|
MockitoAnnotations.initMocks(this);
|
||||||
|
provider =
|
||||||
|
new ZatarCertificateProviderProvider(
|
||||||
|
zatarCertificateProviderFactory, scheduledExecutorServiceFactory, timeProvider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void providerRegisteredName() {
|
||||||
|
CertificateProviderProvider certProviderProvider =
|
||||||
|
CertificateProviderRegistry.getInstance()
|
||||||
|
.getProvider(ZatarCertificateProviderProvider.ZATAR_PROVIDER_NAME);
|
||||||
|
assertThat(certProviderProvider).isInstanceOf(ZatarCertificateProviderProvider.class);
|
||||||
|
ZatarCertificateProviderProvider zatarCertificateProviderProvider =
|
||||||
|
(ZatarCertificateProviderProvider) certProviderProvider;
|
||||||
|
assertThat(zatarCertificateProviderProvider.zatarCertificateProviderFactory)
|
||||||
|
.isSameInstanceAs(ZatarCertificateProvider.Factory.getInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createProvider_minimalConfig() throws IOException {
|
||||||
|
CertificateProvider.DistributorWatcher distWatcher =
|
||||||
|
new CertificateProvider.DistributorWatcher();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, ?> map = (Map<String, ?>) JsonParser.parse(MINIMAL_ZATAR_CONFIG);
|
||||||
|
ScheduledExecutorService mockService = mock(ScheduledExecutorService.class);
|
||||||
|
when(scheduledExecutorServiceFactory.create()).thenReturn(mockService);
|
||||||
|
provider.createCertificateProvider(map, distWatcher, true);
|
||||||
|
verify(zatarCertificateProviderFactory, times(1))
|
||||||
|
.create(
|
||||||
|
eq(distWatcher),
|
||||||
|
eq(true),
|
||||||
|
eq("/var/run/gke-spiffe/certs/..data"),
|
||||||
|
eq("certificates.pem"),
|
||||||
|
eq("private_key.pem"),
|
||||||
|
eq("ca_certificates.pem"),
|
||||||
|
eq(600L),
|
||||||
|
eq(mockService),
|
||||||
|
eq(timeProvider));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createProvider_fullConfig() throws IOException {
|
||||||
|
CertificateProvider.DistributorWatcher distWatcher =
|
||||||
|
new CertificateProvider.DistributorWatcher();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, ?> map = (Map<String, ?>) JsonParser.parse(FULL_ZATAR_CONFIG);
|
||||||
|
ScheduledExecutorService mockService = mock(ScheduledExecutorService.class);
|
||||||
|
when(scheduledExecutorServiceFactory.create()).thenReturn(mockService);
|
||||||
|
provider.createCertificateProvider(map, distWatcher, true);
|
||||||
|
verify(zatarCertificateProviderFactory, times(1))
|
||||||
|
.create(
|
||||||
|
eq(distWatcher),
|
||||||
|
eq(true),
|
||||||
|
eq("/var/run/gke-spiffe/certs/..data1"),
|
||||||
|
eq("certificates2.pem"),
|
||||||
|
eq("private_key3.pem"),
|
||||||
|
eq("ca_certificates4.pem"),
|
||||||
|
eq(7890L),
|
||||||
|
eq(mockService),
|
||||||
|
eq(timeProvider));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createProvider_missingDir_expectException() throws IOException {
|
||||||
|
CertificateProvider.DistributorWatcher distWatcher =
|
||||||
|
new CertificateProvider.DistributorWatcher();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, ?> map = (Map<String, ?>) JsonParser.parse(MISSING_DIR_CONFIG);
|
||||||
|
try {
|
||||||
|
provider.createCertificateProvider(map, distWatcher, true);
|
||||||
|
fail("exception expected");
|
||||||
|
} catch (NullPointerException npe) {
|
||||||
|
assertThat(npe).hasMessageThat().isEqualTo("'directory' is required in the config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createProvider_missingCert_expectException() throws IOException {
|
||||||
|
CertificateProvider.DistributorWatcher distWatcher =
|
||||||
|
new CertificateProvider.DistributorWatcher();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, ?> map = (Map<String, ?>) JsonParser.parse(MISSING_CERT_CONFIG);
|
||||||
|
try {
|
||||||
|
provider.createCertificateProvider(map, distWatcher, true);
|
||||||
|
fail("exception expected");
|
||||||
|
} catch (NullPointerException npe) {
|
||||||
|
assertThat(npe).hasMessageThat().isEqualTo("'certificate-file' is required in the config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createProvider_missingKey_expectException() throws IOException {
|
||||||
|
CertificateProvider.DistributorWatcher distWatcher =
|
||||||
|
new CertificateProvider.DistributorWatcher();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, ?> map = (Map<String, ?>) JsonParser.parse(MISSING_KEY_CONFIG);
|
||||||
|
try {
|
||||||
|
provider.createCertificateProvider(map, distWatcher, true);
|
||||||
|
fail("exception expected");
|
||||||
|
} catch (NullPointerException npe) {
|
||||||
|
assertThat(npe).hasMessageThat().isEqualTo("'private-key-file' is required in the config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createProvider_missingRoot_expectException() throws IOException {
|
||||||
|
CertificateProvider.DistributorWatcher distWatcher =
|
||||||
|
new CertificateProvider.DistributorWatcher();
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, ?> map = (Map<String, ?>) JsonParser.parse(MISSING_ROOT_CONFIG);
|
||||||
|
try {
|
||||||
|
provider.createCertificateProvider(map, distWatcher, true);
|
||||||
|
fail("exception expected");
|
||||||
|
} catch (NullPointerException npe) {
|
||||||
|
assertThat(npe).hasMessageThat().isEqualTo("'ca-certificate-file' is required in the config");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String MINIMAL_ZATAR_CONFIG =
|
||||||
|
"{\n"
|
||||||
|
+ " \"directory\": \"/var/run/gke-spiffe/certs/..data\","
|
||||||
|
+ " \"certificate-file\": \"certificates.pem\","
|
||||||
|
+ " \"private-key-file\": \"private_key.pem\","
|
||||||
|
+ " \"ca-certificate-file\": \"ca_certificates.pem\""
|
||||||
|
+ " }";
|
||||||
|
|
||||||
|
private static final String FULL_ZATAR_CONFIG =
|
||||||
|
"{\n"
|
||||||
|
+ " \"directory\": \"/var/run/gke-spiffe/certs/..data1\","
|
||||||
|
+ " \"certificate-file\": \"certificates2.pem\","
|
||||||
|
+ " \"private-key-file\": \"private_key3.pem\","
|
||||||
|
+ " \"ca-certificate-file\": \"ca_certificates4.pem\","
|
||||||
|
+ " \"refresh-interval\": 7890"
|
||||||
|
+ " }";
|
||||||
|
|
||||||
|
private static final String MISSING_DIR_CONFIG =
|
||||||
|
"{\n"
|
||||||
|
+ " \"certificate-file\": \"certificates.pem\","
|
||||||
|
+ " \"private-key-file\": \"private_key.pem\","
|
||||||
|
+ " \"ca-certificate-file\": \"ca_certificates.pem\""
|
||||||
|
+ " }";
|
||||||
|
|
||||||
|
private static final String MISSING_CERT_CONFIG =
|
||||||
|
"{\n"
|
||||||
|
+ " \"directory\": \"/var/run/gke-spiffe/certs/..data\","
|
||||||
|
+ " \"private-key-file\": \"private_key.pem\","
|
||||||
|
+ " \"ca-certificate-file\": \"ca_certificates.pem\""
|
||||||
|
+ " }";
|
||||||
|
|
||||||
|
private static final String MISSING_KEY_CONFIG =
|
||||||
|
"{\n"
|
||||||
|
+ " \"directory\": \"/var/run/gke-spiffe/certs/..data\","
|
||||||
|
+ " \"certificate-file\": \"certificates.pem\","
|
||||||
|
+ " \"ca-certificate-file\": \"ca_certificates.pem\""
|
||||||
|
+ " }";
|
||||||
|
|
||||||
|
private static final String MISSING_ROOT_CONFIG =
|
||||||
|
"{\n"
|
||||||
|
+ " \"directory\": \"/var/run/gke-spiffe/certs/..data\","
|
||||||
|
+ " \"certificate-file\": \"certificates.pem\","
|
||||||
|
+ " \"private-key-file\": \"private_key.pem\""
|
||||||
|
+ " }";
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue