summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/ConfigServerInfo.java19
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java6
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryCreator.java45
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryUpdater.java132
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java4
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryUpdaterTest.java55
6 files changed, 103 insertions, 158 deletions
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/ConfigServerInfo.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/ConfigServerInfo.java
index 218b2947a21..5dd80961062 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/ConfigServerInfo.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/component/ConfigServerInfo.java
@@ -10,6 +10,7 @@ import com.yahoo.vespa.hosted.node.admin.util.KeyStoreOptions;
import java.net.URI;
import java.nio.file.Paths;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -28,6 +29,7 @@ public class ConfigServerInfo {
private final Optional<KeyStoreOptions> keyStoreOptions;
private final Optional<KeyStoreOptions> trustStoreOptions;
private final Optional<AthenzIdentity> athenzIdentity;
+ private final Optional<ConfigServerConfig.Sia> siaConfig;
public ConfigServerInfo(ConfigServerConfig config) {
this.configServerHostNames = config.hosts();
@@ -46,6 +48,7 @@ public class ConfigServerInfo {
this.athenzIdentity = createAthenzIdentity(
config.athenzDomain(),
config.serviceName());
+ this.siaConfig = verifySiaConfig(config.sia());
}
public List<String> getConfigServerHostNames() {
@@ -77,6 +80,10 @@ public class ConfigServerInfo {
return athenzIdentity;
}
+ public Optional<ConfigServerConfig.Sia> getSiaConfig() {
+ return siaConfig;
+ }
+
private static Map<String, URI> createConfigServerUris(
String scheme,
List<String> configServerHosts,
@@ -86,6 +93,18 @@ public class ConfigServerInfo {
hostname -> URI.create(scheme + "://" + hostname + ":" + port)));
}
+ private static Optional<ConfigServerConfig.Sia> verifySiaConfig(ConfigServerConfig.Sia sia) {
+ List<String> configParams = Arrays.asList(
+ sia.credentialsPath(), sia.configserverIdentityName(), sia.hostIdentityName(), sia.trustStoreFile());
+ if (configParams.stream().allMatch(String::isEmpty)) {
+ return Optional.empty();
+ } else if (configParams.stream().noneMatch(String::isEmpty)) {
+ return Optional.of(sia);
+ } else {
+ throw new IllegalArgumentException("Inconsistent sia config: " + sia);
+ }
+ }
+
private static Optional<KeyStoreOptions> createKeyStoreOptions(String pathToKeyStore, char[] password, String type) {
return Optional.ofNullable(pathToKeyStore)
.filter(path -> !Strings.isNullOrEmpty(path))
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java
index 1405a518625..13af642af4a 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/RealConfigServerClients.java
@@ -29,7 +29,7 @@ public class RealConfigServerClients implements ConfigServerClients {
private final ConfigServerInfo configServerInfo;
public RealConfigServerClients(Environment environment) {
- this(environment.getConfigServerInfo(), environment.getParentHostHostname());
+ this(environment.getConfigServerInfo());
}
/**
@@ -39,9 +39,9 @@ public class RealConfigServerClients implements ConfigServerClients {
* and kept up to date. On failure, this constructor will throw an exception and
* the caller may retry later.
*/
- public RealConfigServerClients(ConfigServerInfo info, String hostname) {
+ public RealConfigServerClients(ConfigServerInfo info) {
this.configServerInfo = info;
- updater = SslConnectionSocketFactoryUpdater.createAndRefreshKeyStoreIfNeeded(info, hostname);
+ updater = SslConnectionSocketFactoryUpdater.createAndRefreshKeyStoreIfNeeded(info);
configServerApi = ConfigServerApiImpl.create(info, updater);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryCreator.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryCreator.java
deleted file mode 100644
index 85b2b426954..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryCreator.java
+++ /dev/null
@@ -1,45 +0,0 @@
-// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.configserver;
-
-import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier;
-import com.yahoo.vespa.athenz.tls.SslContextBuilder;
-import com.yahoo.vespa.hosted.node.admin.component.ConfigServerInfo;
-import com.yahoo.vespa.hosted.node.admin.util.KeyStoreOptions;
-import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
-
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLContext;
-import java.util.Collections;
-import java.util.Optional;
-
-/**
- * @author hakon
- */
-class SslConnectionSocketFactoryCreator {
- SSLConnectionSocketFactory createSocketFactory(
- ConfigServerInfo configServerInfo,
- Optional<KeyStoreOptions> keyStoreOptions) {
- SSLContext context = makeSslContext(configServerInfo, keyStoreOptions);
- return new SSLConnectionSocketFactory(context, makeHostnameVerifier(configServerInfo));
- }
-
- private static SSLContext makeSslContext(
- ConfigServerInfo configServerInfo,
- Optional<KeyStoreOptions> keyStoreOptions) {
- SslContextBuilder sslContextBuilder = new SslContextBuilder();
- configServerInfo.getTrustStoreOptions()
- .map(KeyStoreOptions::loadKeyStore)
- .ifPresent(sslContextBuilder::withTrustStore);
- keyStoreOptions.ifPresent(options ->
- sslContextBuilder.withKeyStore(options.loadKeyStore(), options.password));
-
- return sslContextBuilder.build();
- }
-
- private static HostnameVerifier makeHostnameVerifier(ConfigServerInfo configServerInfo) {
- return configServerInfo.getAthenzIdentity()
- .map(identity -> (HostnameVerifier) new AthenzIdentityVerifier(Collections.singleton(identity)))
- .orElseGet(SSLConnectionSocketFactory::getDefaultHostnameVerifier);
- }
-
-}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryUpdater.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryUpdater.java
index 57fa5526d73..5a97174762c 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryUpdater.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryUpdater.java
@@ -1,91 +1,86 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.configserver;
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
+import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.identity.SiaIdentityProvider;
+import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier;
+import com.yahoo.vespa.athenz.tls.SslContextBuilder;
+import com.yahoo.vespa.athenz.utils.AthenzIdentities;
import com.yahoo.vespa.hosted.node.admin.component.ConfigServerInfo;
-import com.yahoo.vespa.hosted.node.admin.configserver.certificate.ConfigServerKeyStoreRefresher;
-import com.yahoo.vespa.hosted.node.admin.configserver.certificate.ConfigServerKeyStoreRefresherFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import java.io.File;
+import java.nio.file.Paths;
import java.util.HashSet;
-import java.util.Optional;
import java.util.Set;
+import static java.util.Collections.singleton;
+
/**
- * Responsible for updating SSLConnectionSocketFactory on ConfigServerApiImpl asynchronously
- * and as required by embedded certificate expiry
+ * Responsible for updating {@link SSLConnectionSocketFactory} on {@link ConfigServerApiImpl} asynchronously
+ * using SIA based certificates (through {@link SiaIdentityProvider}).
*
+ * @author bjorncs
* @author hakon
*/
public class SslConnectionSocketFactoryUpdater implements AutoCloseable {
- private final ConfigServerInfo configServerInfo;
- private final SslConnectionSocketFactoryCreator socketFactoryCreator;
- // Internal ConfigServerApi used to refresh the key store
- private final ConfigServerApiImpl configServerApi;
- private final Optional<ConfigServerKeyStoreRefresher> keyStoreRefresher;
private final Object monitor = new Object();
- private SSLConnectionSocketFactory socketFactory = null;
+ private final HostnameVerifier configServerHostnameVerifier;
+ private final SiaIdentityProvider sia;
+
private final Set<ConfigServerApi> configServerApis = new HashSet<>();
+ private SSLConnectionSocketFactory socketFactory;
/**
* Creates an updater with valid initial {@link SSLConnectionSocketFactory}
*
- * @param hostname the hostname of localhost
* @throws RuntimeException if e.g. key store options have been specified, but was unable
* create a create a key store with a valid certificate
*/
- public static SslConnectionSocketFactoryUpdater createAndRefreshKeyStoreIfNeeded(
- ConfigServerInfo configServerInfo, String hostname) {
- return new SslConnectionSocketFactoryUpdater(
- configServerInfo,
- hostname,
- ConfigServerKeyStoreRefresher::new,
- new SslConnectionSocketFactoryCreator());
+ public static SslConnectionSocketFactoryUpdater createAndRefreshKeyStoreIfNeeded(ConfigServerInfo configServerInfo) {
+ SiaIdentityProvider siaIdentityProvider = configServerInfo.getSiaConfig()
+ .map(siaConfig ->
+ new SiaIdentityProvider(
+ (AthenzService) AthenzIdentities.from(siaConfig.hostIdentityName()),
+ Paths.get(siaConfig.credentialsPath()),
+ new File(siaConfig.trustStoreFile())))
+ .orElse(null);
+ HostnameVerifier configServerHostnameVerifier = configServerInfo.getSiaConfig()
+ .map(siaConfig -> createHostnameVerifier(AthenzIdentities.from(siaConfig.configserverIdentityName())))
+ .orElseGet(SSLConnectionSocketFactory::getDefaultHostnameVerifier);
+ return new SslConnectionSocketFactoryUpdater(siaIdentityProvider, configServerHostnameVerifier);
}
- /** Non-private for testing only */
- SslConnectionSocketFactoryUpdater(
- ConfigServerInfo configServerInfo,
- String hostname,
- ConfigServerKeyStoreRefresherFactory refresherFactory,
- SslConnectionSocketFactoryCreator socketFactoryCreator) {
- this.configServerInfo = configServerInfo;
- this.socketFactoryCreator = socketFactoryCreator;
-
- // ConfigServerApi used to refresh the key store. Does not itself rely on a socket
- // factory with key store, of course.
- SSLConnectionSocketFactory socketFactoryWithoutKeyStore =
- socketFactoryCreator.createSocketFactory(configServerInfo, Optional.empty());
- configServerApi = ConfigServerApiImpl.createWithSocketFactory(
- configServerInfo.getConfigServerUris(), socketFactoryWithoutKeyStore);
-
- // If we have keystore options, we should make sure we use the keystore with the latest certificate,
- // start the keystore refresher.
- keyStoreRefresher = configServerInfo.getKeyStoreOptions().map(keyStoreOptions -> {
- ConfigServerKeyStoreRefresher keyStoreRefresher = refresherFactory.create(
- keyStoreOptions,
- this::updateSslConnectionSocketFactory,
- configServerApi,
- hostname);
-
- // Run the refresh once manually to make sure that we have a valid certificate, otherwise fail.
- try {
- keyStoreRefresher.refreshKeyStoreIfNeeded();
- updateSslConnectionSocketFactory();
- } catch (Exception e) {
- throw new RuntimeException("Failed to acquire certificate to config server", e);
- }
-
- keyStoreRefresher.start();
- return keyStoreRefresher;
- });
+ SslConnectionSocketFactoryUpdater(SiaIdentityProvider siaIdentityProvider,
+ HostnameVerifier configServerHostnameVerifier) {
+ this.configServerHostnameVerifier = configServerHostnameVerifier;
+ this.sia = siaIdentityProvider;
+ if (siaIdentityProvider != null) {
+ siaIdentityProvider.addReloadListener(this::updateSocketFactory);
+ socketFactory = createSocketFactory(siaIdentityProvider.getIdentitySslContext());
+ } else {
+ socketFactory = createDefaultSslConnectionSocketFactory();
+ }
+ }
+
+ private void updateSocketFactory(SSLContext sslContext) {
+ synchronized (monitor) {
+ socketFactory = createSocketFactory(sslContext);
+ configServerApis.forEach(api -> api.setSSLConnectionSocketFactory(socketFactory));
+ }
}
public SSLConnectionSocketFactory getCurrentSocketFactory() {
- return socketFactory;
+ synchronized (monitor) {
+ return socketFactory;
+ }
}
- /** Register a {@link ConfigServerApi} whose SSLConnectionSocketFactory will be kept up to date */
+ /** Register a {@link ConfigServerApi} whose {@link SSLConnectionSocketFactory} will be kept up to date */
public void registerConfigServerApi(ConfigServerApi configServerApi) {
synchronized (monitor) {
configServerApi.setSSLConnectionSocketFactory(socketFactory);
@@ -101,17 +96,22 @@ public class SslConnectionSocketFactoryUpdater implements AutoCloseable {
@Override
public void close() {
- keyStoreRefresher.ifPresent(ConfigServerKeyStoreRefresher::stop);
- configServerApi.close();
+ if (sia != null) {
+ sia.deconstruct();
+ }
}
- private void updateSslConnectionSocketFactory() {
- synchronized (monitor) {
- socketFactory = socketFactoryCreator.createSocketFactory(
- configServerInfo,
- configServerInfo.getKeyStoreOptions());
+ private SSLConnectionSocketFactory createSocketFactory(SSLContext sslContext) {
+ return new SSLConnectionSocketFactory(sslContext, configServerHostnameVerifier);
+ }
- configServerApis.forEach(api -> api.setSSLConnectionSocketFactory(socketFactory));
- }
+ private SSLConnectionSocketFactory createDefaultSslConnectionSocketFactory() {
+ SSLContext sslContext = new SslContextBuilder().build();
+ return createSocketFactory(sslContext);
+ }
+
+ private static HostnameVerifier createHostnameVerifier(AthenzIdentity identity) {
+ return new AthenzIdentityVerifier(singleton(identity));
}
+
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java
index 587e6212ad4..f9d0736fe21 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/provider/NodeAdminProvider.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.hosted.node.admin.provider;
import com.google.inject.Inject;
import com.yahoo.concurrent.classlock.ClassLocking;
import com.yahoo.container.di.componentgraph.Provider;
-import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.hosted.dockerapi.Docker;
import com.yahoo.vespa.hosted.dockerapi.metrics.MetricReceiverWrapper;
import com.yahoo.vespa.hosted.node.admin.component.ConfigServerInfo;
@@ -22,8 +21,7 @@ public class NodeAdminProvider implements Provider<NodeAdminStateUpdater> {
MetricReceiverWrapper metricReceiver,
ClassLocking classLocking) {
ConfigServerClients clients = new RealConfigServerClients(
- new ConfigServerInfo(configServerConfig),
- Defaults.getDefaults().vespaHostname());
+ new ConfigServerInfo(configServerConfig));
dockerAdmin = new DockerAdminComponent(configServerConfig,
docker,
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryUpdaterTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryUpdaterTest.java
index 490f45b094c..6c6f8cf40fe 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryUpdaterTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/SslConnectionSocketFactoryUpdaterTest.java
@@ -1,58 +1,31 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.configserver;
-import com.yahoo.vespa.hosted.node.admin.component.ConfigServerInfo;
-import com.yahoo.vespa.hosted.node.admin.configserver.certificate.ConfigServerKeyStoreRefresher;
-import com.yahoo.vespa.hosted.node.admin.configserver.certificate.ConfigServerKeyStoreRefresherFactory;
-import com.yahoo.vespa.hosted.node.admin.util.KeyStoreOptions;
-import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
-import org.junit.Before;
+import com.yahoo.vespa.athenz.identity.SiaIdentityProvider;
+import com.yahoo.vespa.athenz.tls.SslContextBuilder;
import org.junit.Test;
-import java.util.Optional;
-
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
+import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
- * @author hakon
+ * @author bjorncs
*/
public class SslConnectionSocketFactoryUpdaterTest {
- private final ConfigServerInfo configServerInfo = mock(ConfigServerInfo.class);
- private final String hostname = "host.oath.com";
- private final ConfigServerKeyStoreRefresherFactory refresherFactory =
- mock(ConfigServerKeyStoreRefresherFactory.class);
- private final ConfigServerKeyStoreRefresher refresher =
- mock(ConfigServerKeyStoreRefresher.class);
- private final SslConnectionSocketFactoryCreator socketFactoryCreator =
- mock(SslConnectionSocketFactoryCreator.class);
- private final SSLConnectionSocketFactory socketFactory = mock(SSLConnectionSocketFactory.class);
- @Before
- public void setUp() {
- KeyStoreOptions keyStoreOptions = mock(KeyStoreOptions.class);
- when(configServerInfo.getKeyStoreOptions()).thenReturn(Optional.of(keyStoreOptions));
- when(refresherFactory.create(any(), any(), any(), any())).thenReturn(refresher);
- when(socketFactoryCreator.createSocketFactory(any(), any()))
- .thenReturn(socketFactory);
+ @Test
+ public void creates_default_ssl_connection_factory_when_no_sia_provided() {
+ SslConnectionSocketFactoryUpdater updater =
+ new SslConnectionSocketFactoryUpdater(null, (hostname, session) -> true);
+ assertNotNull(updater.getCurrentSocketFactory());
}
@Test
- public void testSettingOfSocketFactory() {
- SslConnectionSocketFactoryUpdater updater = new SslConnectionSocketFactoryUpdater(
- configServerInfo,
- hostname,
- refresherFactory,
- socketFactoryCreator);
-
- assertTrue(socketFactory == updater.getCurrentSocketFactory());
-
- ConfigServerApi api = mock(ConfigServerApi.class);
- updater.registerConfigServerApi(api);
- verify(api, times(1)).setSSLConnectionSocketFactory(socketFactory);
+ public void creates_ssl_connection_factory_when_sia_provided() {
+ SiaIdentityProvider sia = mock(SiaIdentityProvider.class);
+ when(sia.getIdentitySslContext()).thenReturn(new SslContextBuilder().build());
+ SslConnectionSocketFactoryUpdater updater = new SslConnectionSocketFactoryUpdater(sia, (hostname, session) -> true);
+ assertNotNull(updater.getCurrentSocketFactory());
}
} \ No newline at end of file