aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorn.christian@seime.no>2017-11-16 09:55:03 +0100
committerGitHub <noreply@github.com>2017-11-16 09:55:03 +0100
commit3982fd81899e6a1942b42e1f05dea3ee29e41fb6 (patch)
tree5af38e4b268ecde2bb89cfdb93683897ea200b70
parente429e4e4fa1d5225d2c16d457bebd420f26b0a3b (diff)
parent26a183be1a9f6bad2d0206449666e3a98a5b7c74 (diff)
Merge pull request #4132 from vespa-engine/bjorncs/custom-ssl-context
Bjorncs/custom ssl context
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/Container.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java63
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java4
-rw-r--r--config-model/src/main/resources/schema/containercluster.rnc1
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java38
-rw-r--r--config-model/src/test/schema-test-files/services.xml4
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java70
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreConfigurator.java95
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreContext.java51
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStore.java12
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreConfigurator.java14
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreContext.java16
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/package-info.java4
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java8
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/JksKeyStore.java (renamed from jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/jks/JksKeyStore.java)14
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/SslContextFactory.java2
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java5
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java24
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDriver.java2
19 files changed, 301 insertions, 128 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java
index 5915f0cea0b..8991bfa6215 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java
@@ -201,7 +201,7 @@ public class Container extends AbstractService implements
}
private void initDefaultJettyConnector() {
- defaultHttpServer.addConnector(new ConnectorFactory("SearchServer", getSearchPort(), null));
+ defaultHttpServer.addConnector(new ConnectorFactory("SearchServer", getSearchPort()));
}
private boolean hasDocproc() {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java
index 9d3d8b32ddb..d9c9050780c 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ConnectorFactory.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.model.container.http;
import com.yahoo.component.ComponentId;
import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.jdisc.http.ConnectorConfig;
+import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreConfigurator;
import com.yahoo.osgi.provider.model.ComponentModel;
import com.yahoo.text.XML;
import com.yahoo.vespa.model.container.component.SimpleComponent;
@@ -13,31 +14,48 @@ import static com.yahoo.component.ComponentSpecification.fromString;
import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType;
/**
- * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
- * @since 5.21.0
+ * @author Einar M R Rosenvinge
+ * @author bjorncs
*/
public class ConnectorFactory extends SimpleComponent implements ConnectorConfig.Producer {
private final String name;
- private volatile int listenPort;
+ private final int listenPort;
private final Element legacyConfig;
- public ConnectorFactory(final String name, final int listenPort, final Element legacyConfig) {
+ public ConnectorFactory(String name, int listenPort) {
+ this(name, listenPort, null, null);
+ }
+
+ public ConnectorFactory(String name, int listenPort, Element legacyConfig, Element sslKeystoreConfigurator) {
super(new ComponentModel(
new BundleInstantiationSpecification(new ComponentId(name),
fromString("com.yahoo.jdisc.http.server.jetty.ConnectorFactory"),
- fromString("jdisc_http_service"))
-
- ));
-
-
+ fromString("jdisc_http_service"))));
this.name = name;
this.listenPort = listenPort;
this.legacyConfig = legacyConfig;
+ addChild(getSslKeyStoreConfigurator(name, sslKeystoreConfigurator));
}
@Override
public void getConfig(ConnectorConfig.Builder connectorBuilder) {
+ configureWithLegacyHttpConfig(legacyConfig, connectorBuilder);
+ connectorBuilder.listenPort(listenPort);
+ connectorBuilder.name(name);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public int getListenPort() {
+ return listenPort;
+ }
+
+ // TODO Remove support for legacy config in Vespa 7
+ @Deprecated
+ private static void configureWithLegacyHttpConfig(Element legacyConfig, ConnectorConfig.Builder connectorBuilder) {
if (legacyConfig != null) {
{
Element tcpKeepAliveEnabled = XML.getChild(legacyConfig, "tcpKeepAliveEnabled");
@@ -85,9 +103,7 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig
Element ssl = XML.getChild(legacyConfig, "ssl");
Element sslEnabled = XML.getChild(ssl, "enabled");
- if (ssl != null &&
- sslEnabled != null &&
- Boolean.parseBoolean(XML.getValue(sslEnabled).trim())) {
+ if (ssl != null && sslEnabled != null && Boolean.parseBoolean(XML.getValue(sslEnabled).trim())) {
ConnectorConfig.Ssl.Builder sslBuilder = new ConnectorConfig.Ssl.Builder();
sslBuilder.enabled(true);
{
@@ -129,21 +145,18 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig
connectorBuilder.ssl(sslBuilder);
}
}
-
- connectorBuilder.listenPort(listenPort);
- connectorBuilder.name(name);
- }
-
- public String getName() {
- return name;
}
- public int getListenPort() {
- return listenPort;
- }
-
- public void setListenPort(int httpPort) {
- this.listenPort = httpPort;
+ private static SimpleComponent getSslKeyStoreConfigurator(String name, Element sslKeystoreConfigurator) {
+ String idSpec = "ssl-keystore-configurator@" + name;
+ if (sslKeystoreConfigurator != null) {
+ String className = sslKeystoreConfigurator.getAttribute("class");
+ String bundleName = sslKeystoreConfigurator.getAttribute("bundle");
+ return new SimpleComponent(new ComponentModel(idSpec, className, bundleName));
+ } else {
+ return new SimpleComponent(
+ new ComponentModel(idSpec, DefaultSslKeyStoreConfigurator.class.getName(), "jdisc_http_service"));
+ }
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java
index 6271ff817bb..f2012a609a7 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/JettyConnectorBuilder.java
@@ -33,6 +33,8 @@ public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuil
legacyServerConfig = null;
}
}
- return new ConnectorFactory(name, port, legacyServerConfig);
+ Element sslKeystoreConfigurator = XML.getChild(serverSpec, "ssl-keystore-configurator");
+ return new ConnectorFactory(name, port, legacyServerConfig, sslKeystoreConfigurator);
}
+
}
diff --git a/config-model/src/main/resources/schema/containercluster.rnc b/config-model/src/main/resources/schema/containercluster.rnc
index af9b89b8553..47cf0638d72 100644
--- a/config-model/src/main/resources/schema/containercluster.rnc
+++ b/config-model/src/main/resources/schema/containercluster.rnc
@@ -62,6 +62,7 @@ Filtering = element filtering {
HttpServer = element server {
attribute port { xsd:nonNegativeInteger } &
ComponentId &
+ element ssl-keystore-configurator { BundleSpec }? & # FOR INTERNAL USE ONLY - SUBJECT TO CHANGE
GenericConfig*
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java
index 4621b5ebe50..06dad19f011 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/JettyContainerModelBuilderTest.java
@@ -3,13 +3,19 @@ package com.yahoo.vespa.model.container.xml;
import com.yahoo.config.model.builder.xml.test.DomBuilderTest;
import com.yahoo.container.ComponentsConfig;
+import com.yahoo.container.bundle.BundleInstantiationSpecification;
import com.yahoo.container.jdisc.FilterBindingsProvider;
import com.yahoo.jdisc.http.ConnectorConfig;
+import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreConfigurator;
import com.yahoo.vespa.model.container.ContainerCluster;
+import com.yahoo.vespa.model.container.component.SimpleComponent;
+import com.yahoo.vespa.model.container.http.ConnectorFactory;
import com.yahoo.vespa.model.container.http.JettyHttpServer;
import org.junit.Test;
import org.w3c.dom.Element;
+import org.xml.sax.SAXException;
+import java.io.IOException;
import java.util.List;
import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType;
@@ -182,6 +188,38 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas
is(not(nullValue())));
}
+ @Test
+ public void ssl_keystore_configurator_can_be_overriden() throws IOException, SAXException {
+ Element clusterElem = DomBuilderTest.parse(
+ "<jdisc id='default' version='1.0' jetty='true'>",
+ " <http>",
+ " <server port='9000' id='foo'>",
+ " <ssl-keystore-configurator class='com.yahoo.MySslKeyStoreConfigurator' bundle='mybundle'/>",
+ " </server>",
+ " <server port='9001' id='bar'/>",
+ " </http>",
+ nodesXml,
+ "</jdisc>");
+ createModel(root, clusterElem);
+ ContainerCluster cluster = (ContainerCluster) root.getChildren().get("default");
+ List<ConnectorFactory> connectorFactories = cluster.getChildrenByTypeRecursive(ConnectorFactory.class);
+
+ {
+ ConnectorFactory firstConnector = connectorFactories.get(0);
+ SimpleComponent sslKeystoreConfigurator = firstConnector.getChildrenByTypeRecursive(SimpleComponent.class).get(0);
+ BundleInstantiationSpecification spec = sslKeystoreConfigurator.model.bundleInstantiationSpec;
+ assertThat(spec.classId.toString(), is("com.yahoo.MySslKeyStoreConfigurator"));
+ assertThat(spec.bundle.toString(), is("mybundle"));
+ }
+ {
+ ConnectorFactory secondFactory = connectorFactories.get(1);
+ SimpleComponent sslKeystoreConfigurator = secondFactory.getChildrenByTypeRecursive(SimpleComponent.class).get(0);
+ BundleInstantiationSpecification spec = sslKeystoreConfigurator.model.bundleInstantiationSpec;
+ assertThat(spec.classId.toString(), is(DefaultSslKeyStoreConfigurator.class.getName()));
+ assertThat(spec.bundle.toString(), is("jdisc_http_service"));
+ }
+ }
+
private void assertJettyServerInConfig() {
ContainerCluster cluster = (ContainerCluster) root.getChildren().get("default");
List<JettyHttpServer> jettyServers = cluster.getChildrenByTypeRecursive(JettyHttpServer.class);
diff --git a/config-model/src/test/schema-test-files/services.xml b/config-model/src/test/schema-test-files/services.xml
index 18468dcc433..98637c03020 100644
--- a/config-model/src/test/schema-test-files/services.xml
+++ b/config-model/src/test/schema-test-files/services.xml
@@ -110,7 +110,9 @@
</request-chain>
</filtering>
- <server port="4080" id="myServer" />
+ <server port="4080" id="myServer">
+ <ssl-keystore-configurator class="com.yahoo.MySslKeyStoreConfigurator" bundle="mybundle" />
+ </server>
<server port="4081" id="anotherServer">
<config name="container.jdisc.config.http-server">
<maxChunkSize>9999</maxChunkSize>
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java
index 7ec51f35b74..8255e16e0ee 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactory.java
@@ -5,9 +5,9 @@ import com.google.inject.Inject;
import com.yahoo.jdisc.Metric;
import com.yahoo.jdisc.http.ConnectorConfig;
import com.yahoo.jdisc.http.ConnectorConfig.Ssl;
-import com.yahoo.jdisc.http.ConnectorConfig.Ssl.PemKeyStore;
import com.yahoo.jdisc.http.SecretStore;
-import com.yahoo.jdisc.http.ssl.pem.PemSslKeyStore;
+import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreContext;
+import com.yahoo.jdisc.http.ssl.SslKeyStoreConfigurator;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
@@ -17,16 +17,7 @@ import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;
-import java.io.IOException;
-import java.io.UncheckedIOException;
import java.nio.channels.ServerSocketChannel;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.KeyStore;
-import java.util.logging.Logger;
-
-import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType.Enum.JKS;
-import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType.Enum.PEM;
/**
* @author Einar M R Rosenvinge
@@ -34,14 +25,17 @@ import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType.Enum.PEM;
*/
public class ConnectorFactory {
- private final static Logger log = Logger.getLogger(ConnectorFactory.class.getName());
private final ConnectorConfig connectorConfig;
private final SecretStore secretStore;
+ private final SslKeyStoreConfigurator sslKeyStoreConfigurator;
@Inject
- public ConnectorFactory(ConnectorConfig connectorConfig, SecretStore secretStore) {
+ public ConnectorFactory(ConnectorConfig connectorConfig,
+ SecretStore secretStore,
+ SslKeyStoreConfigurator sslKeyStoreConfigurator) {
this.connectorConfig = connectorConfig;
this.secretStore = secretStore;
+ this.sslKeyStoreConfigurator = sslKeyStoreConfigurator;
if (connectorConfig.ssl().enabled())
validateSslConfig(connectorConfig);
@@ -50,30 +44,6 @@ public class ConnectorFactory {
// TODO: can be removed when we have dedicated SSL config in services.xml
private static void validateSslConfig(ConnectorConfig config) {
ConnectorConfig.Ssl ssl = config.ssl();
-
- if (ssl.keyStoreType() == JKS) {
- if (!ssl.pemKeyStore().keyPath().isEmpty() || ! ssl.pemKeyStore().certificatePath().isEmpty()) {
- throw new IllegalArgumentException("pemKeyStore attributes can not be set when keyStoreType is JKS.");
- }
- if (ssl.keyDbKey().isEmpty()) {
- throw new IllegalArgumentException("Missing password for JKS keystore");
- }
- }
- if (ssl.keyStoreType() == PEM) {
- if (! ssl.keyStorePath().isEmpty()) {
- throw new IllegalArgumentException("keyStorePath can not be set when keyStoreType is PEM");
- }
- if (!ssl.keyDbKey().isEmpty()) {
- // TODO Make an error once there are separate passwords for truststore and keystore
- log.warning("Encrypted PEM key stores are not supported. Password is only applied to truststore");
- }
- if (ssl.pemKeyStore().certificatePath().isEmpty()) {
- throw new IllegalArgumentException("Missing certificate path.");
- }
- if (ssl.pemKeyStore().keyPath().isEmpty()) {
- throw new IllegalArgumentException("Missing key path.");
- }
- }
if (!ssl.trustStorePath().isEmpty() && ssl.useTrustStorePassword() && ssl.keyDbKey().isEmpty()) {
throw new IllegalArgumentException("Missing password for JKS truststore");
}
@@ -128,6 +98,9 @@ public class ConnectorFactory {
Ssl sslConfig = connectorConfig.ssl();
SslContextFactory factory = new SslContextFactory();
+
+ sslKeyStoreConfigurator.configure(new DefaultSslKeyStoreContext(factory));
+
switch (sslConfig.clientAuth()) {
case NEED_AUTH:
factory.setNeedClientAuth(true);
@@ -172,16 +145,6 @@ public class ConnectorFactory {
}
String keyDbPassword = sslConfig.keyDbKey();
- switch (sslConfig.keyStoreType()) {
- case PEM:
- factory.setKeyStore(createPemKeyStore(sslConfig.pemKeyStore()));
- break;
- case JKS:
- factory.setKeyStorePath(sslConfig.keyStorePath());
- factory.setKeyStoreType(sslConfig.keyStoreType().toString());
- factory.setKeyStorePassword(secretStore.getSecret(keyDbPassword));
- break;
- }
if (!sslConfig.trustStorePath().isEmpty()) {
factory.setTrustStorePath(sslConfig.trustStorePath());
@@ -196,17 +159,4 @@ public class ConnectorFactory {
return new SslConnectionFactory(factory, HttpVersion.HTTP_1_1.asString());
}
- private static KeyStore createPemKeyStore(PemKeyStore pemKeyStore) {
- try {
- Path certificatePath = Paths.get(pemKeyStore.certificatePath());
- Path keyPath = Paths.get(pemKeyStore.keyPath());
- return new PemSslKeyStore(certificatePath, keyPath)
- .loadJavaKeyStore();
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- } catch (Exception e) {
- throw new RuntimeException("Failed setting up key store for " + pemKeyStore.keyPath() + ", " + pemKeyStore.certificatePath(), e);
- }
- }
-
}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreConfigurator.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreConfigurator.java
new file mode 100644
index 00000000000..fb0a5869bb3
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreConfigurator.java
@@ -0,0 +1,95 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.ssl;
+
+import com.google.inject.Inject;
+import com.yahoo.jdisc.http.ConnectorConfig;
+import com.yahoo.jdisc.http.SecretStore;
+import com.yahoo.jdisc.http.ssl.pem.PemSslKeyStore;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.util.logging.Logger;
+
+/**
+ * @author bjorncs
+ */
+public class DefaultSslKeyStoreConfigurator implements SslKeyStoreConfigurator {
+
+ private static final Logger log = Logger.getLogger(DefaultSslKeyStoreConfigurator.class.getName());
+
+ private final SecretStore secretStore;
+ private final ConnectorConfig.Ssl config;
+
+ @Inject
+ public DefaultSslKeyStoreConfigurator(ConnectorConfig config, SecretStore secretStore) {
+ validateConfig(config.ssl());
+ this.secretStore = secretStore;
+ this.config = config.ssl();
+ }
+
+ private static void validateConfig(ConnectorConfig.Ssl config) {
+ if (!config.enabled()) return;
+ switch (config.keyStoreType()) {
+ case JKS:
+ validateJksConfig(config);
+ break;
+ case PEM:
+ validatePemConfig(config);
+ break;
+ }
+ }
+
+ @Override
+ public void configure(SslKeyStoreContext context) {
+ if (!config.enabled()) return;
+ switch (config.keyStoreType()) {
+ case JKS:
+ context.updateKeyStore(config.keyStorePath(), "JKS", secretStore.getSecret(config.keyDbKey()));
+ break;
+ case PEM:
+ context.updateKeyStore(createPemKeyStore(config.pemKeyStore()));
+ break;
+ }
+ }
+
+ private static void validateJksConfig(ConnectorConfig.Ssl ssl) {
+ if (!ssl.pemKeyStore().keyPath().isEmpty() || ! ssl.pemKeyStore().certificatePath().isEmpty()) {
+ throw new IllegalArgumentException("pemKeyStore attributes can not be set when keyStoreType is JKS.");
+ }
+ if (ssl.keyDbKey().isEmpty()) {
+ throw new IllegalArgumentException("Missing password for JKS keystore");
+ }
+ }
+
+ private static void validatePemConfig(ConnectorConfig.Ssl ssl) {
+ if (! ssl.keyStorePath().isEmpty()) {
+ throw new IllegalArgumentException("keyStorePath can not be set when keyStoreType is PEM");
+ }
+ if (!ssl.keyDbKey().isEmpty()) {
+ // TODO Make an error once there are separate passwords for truststore and keystore
+ log.warning("Encrypted PEM key stores are not supported. Password is only applied to truststore");
+ }
+ if (ssl.pemKeyStore().certificatePath().isEmpty()) {
+ throw new IllegalArgumentException("Missing certificate path.");
+ }
+ if (ssl.pemKeyStore().keyPath().isEmpty()) {
+ throw new IllegalArgumentException("Missing key path.");
+ }
+ }
+
+ private static KeyStore createPemKeyStore(ConnectorConfig.Ssl.PemKeyStore pemKeyStore) {
+ try {
+ Path certificatePath = Paths.get(pemKeyStore.certificatePath());
+ Path keyPath = Paths.get(pemKeyStore.keyPath());
+ return new PemSslKeyStore(certificatePath, keyPath).loadJavaKeyStore();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed setting up key store for " + pemKeyStore.keyPath() + ", " + pemKeyStore.certificatePath(), e);
+ }
+ }
+
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreContext.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreContext.java
new file mode 100644
index 00000000000..8a95893eaeb
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/DefaultSslKeyStoreContext.java
@@ -0,0 +1,51 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.ssl;
+
+import org.eclipse.jetty.util.ssl.SslContextFactory;
+
+import java.security.KeyStore;
+import java.util.function.Consumer;
+
+/**
+ * @author bjorncs
+ */
+public class DefaultSslKeyStoreContext implements SslKeyStoreContext {
+
+ private final SslContextFactory sslContextFactory;
+
+ public DefaultSslKeyStoreContext(SslContextFactory sslContextFactory) {
+ this.sslContextFactory = sslContextFactory;
+ }
+
+ @Override
+ public void updateKeyStore(KeyStore keyStore) {
+ updateKeyStore(keyStore, null);
+ }
+
+ @Override
+ public void updateKeyStore(KeyStore keyStore, String password) {
+ updateKeyStore(sslContextFactory -> {
+ sslContextFactory.setKeyStore(keyStore);
+ if (password != null) {
+ sslContextFactory.setKeyStorePassword(null);
+ }
+ });
+ }
+
+ @Override
+ public void updateKeyStore(String keyStorePath, String keyStoreType, String keyStorePassword) {
+ updateKeyStore(sslContextFactory -> {
+ sslContextFactory.setKeyStorePath(keyStorePath);
+ sslContextFactory.setKeyStoreType(keyStoreType);
+ sslContextFactory.setKeyStorePassword(keyStorePassword);
+ });
+ }
+
+ private void updateKeyStore(Consumer<SslContextFactory> reloader) {
+ try {
+ sslContextFactory.reload(reloader);
+ } catch (Exception e) {
+ throw new RuntimeException("Could not update keystore: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStore.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStore.java
deleted file mode 100644
index c282c94c1bd..00000000000
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStore.java
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.jdisc.http.ssl;
-
-import java.security.KeyStore;
-
-/**
- *
- * @author bjorncs
- */
-public interface SslKeyStore {
- KeyStore loadJavaKeyStore() throws Exception;
-}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreConfigurator.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreConfigurator.java
new file mode 100644
index 00000000000..619f4a636ed
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreConfigurator.java
@@ -0,0 +1,14 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.ssl;
+
+/**
+ * An interface for an component that can configure an {@link SslKeyStoreContext}. The implementor can assume that
+ * the {@link SslKeyStoreContext} instance is thread-safe and be updated at any time
+ * during and after the call to{@link #configure(SslKeyStoreContext)}.
+ * Modifying the {@link SslKeyStoreContext} instance will trigger a hot reload of the keystore in JDisc.
+ *
+ * @author bjorncs
+ */
+public interface SslKeyStoreConfigurator {
+ void configure(SslKeyStoreContext context);
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreContext.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreContext.java
new file mode 100644
index 00000000000..2a25f6d78b5
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/SslKeyStoreContext.java
@@ -0,0 +1,16 @@
+// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.ssl;
+
+import java.security.KeyStore;
+
+/**
+ * An interface to update the keystore in JDisc. Any update will trigger a hot reload and new connections will
+ * immediately see the new certificate chain.
+ *
+ * @author bjorncs
+ */
+public interface SslKeyStoreContext {
+ void updateKeyStore(KeyStore keyStore);
+ void updateKeyStore(KeyStore keyStore, String password);
+ void updateKeyStore(String keyStorePath, String keyStoreType, String keyStorePassword);
+}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/package-info.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/package-info.java
index c47d36991d4..5f817d4cfc2 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/package-info.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/package-info.java
@@ -1,4 +1,8 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * @author bjorncs
+ */
@ExportPackage
package com.yahoo.jdisc.http.ssl;
+
import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java
index 9f0a635f7c1..2ae1894a8d4 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/pem/PemSslKeyStore.java
@@ -1,7 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.http.ssl.pem;
-import com.yahoo.jdisc.http.ssl.SslKeyStore;
import com.yahoo.jdisc.http.ssl.pem.PemKeyStore.KeyStoreLoadParameter;
import com.yahoo.jdisc.http.ssl.pem.PemKeyStore.TrustStoreLoadParameter;
@@ -21,7 +20,7 @@ import java.security.cert.CertificateException;
* @author Tony Vaagenes
* @author bjorncs
*/
-public class PemSslKeyStore implements SslKeyStore {
+public class PemSslKeyStore {
static {
Security.addProvider(new PemKeyStoreProvider());
@@ -40,9 +39,8 @@ public class PemSslKeyStore implements SslKeyStore {
this.loadParameter = new TrustStoreLoadParameter(certificatePath);
}
- @Override
- public KeyStore loadJavaKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
- //cached since Reader(in loadParameter) can only be used one time.
+ public KeyStore loadJavaKeyStore()
+ throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
if (keyStore == null) {
keyStore = KeyStore.getInstance(KEY_STORE_TYPE);
keyStore.load(loadParameter);
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/jks/JksKeyStore.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/JksKeyStore.java
index 9cb040fb97d..1c7a917c688 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/jks/JksKeyStore.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/JksKeyStore.java
@@ -1,24 +1,19 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.jdisc.http.ssl.jks;
+package com.yahoo.jdisc.http;
-import com.yahoo.jdisc.http.ssl.SslKeyStore;
-
-import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.CertificateException;
/**
* @author Tony Vaagenes
* @author bjorncs
*/
-public class JksKeyStore implements SslKeyStore {
+public class JksKeyStore {
private static final String KEY_STORE_TYPE = "JKS";
+
private final Path keyStoreFile;
private final String keyStorePassword;
@@ -35,8 +30,7 @@ public class JksKeyStore implements SslKeyStore {
return keyStorePassword;
}
- @Override
- public KeyStore loadJavaKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
+ public KeyStore loadJavaKeyStore() throws Exception {
try(InputStream stream = Files.newInputStream(keyStoreFile)) {
KeyStore keystore = KeyStore.getInstance(KEY_STORE_TYPE);
keystore.load(stream, keyStorePassword != null ? keyStorePassword.toCharArray() : null);
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/SslContextFactory.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/SslContextFactory.java
index 5dd5dca1667..d86516df453 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/SslContextFactory.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/SslContextFactory.java
@@ -1,8 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.http;
-import com.yahoo.jdisc.http.ssl.jks.JksKeyStore;
-
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java
index 1200a06be2c..0d8f433cc39 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/guiceModules/ConnectorFactoryRegistryModule.java
@@ -11,6 +11,7 @@ import com.yahoo.jdisc.http.ConnectorConfig.Builder;
import com.yahoo.jdisc.http.SecretStore;
import com.yahoo.jdisc.http.server.jetty.ConnectorFactory;
import com.yahoo.jdisc.http.server.jetty.TestDrivers;
+import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreConfigurator;
/**
* Guice module for test ConnectorFactories
@@ -46,7 +47,9 @@ public class ConnectorFactoryRegistryModule implements Module {
private static class StaticKeyDbConnectorFactory extends ConnectorFactory {
public StaticKeyDbConnectorFactory(ConnectorConfig connectorConfig) {
- super(connectorConfig, new MockSecretStore());
+ super(connectorConfig,
+ new MockSecretStore(),
+ new DefaultSslKeyStoreConfigurator(connectorConfig, new MockSecretStore()));
}
}
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java
index fceec51231a..781bc6a7b5f 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/ConnectorFactoryTest.java
@@ -4,6 +4,7 @@ package com.yahoo.jdisc.http.server.jetty;
import com.yahoo.jdisc.Metric;
import com.yahoo.jdisc.http.ConnectorConfig;
import com.yahoo.jdisc.http.SecretStore;
+import com.yahoo.jdisc.http.ssl.DefaultSslKeyStoreConfigurator;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.AbstractHandler;
@@ -23,7 +24,7 @@ import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType.Enum.PEM;
import static org.hamcrest.CoreMatchers.equalTo;
/**
- * @author <a href="mailto:einarmr@yahoo-inc.com">Einar M R Rosenvinge</a>
+ * @author Einar M R Rosenvinge
*/
public class ConnectorFactoryTest {
@@ -37,8 +38,7 @@ public class ConnectorFactoryTest {
.pemKeyStore(
new Ssl.PemKeyStore.Builder()
.keyPath("nonEmpty"))));
-
- ConnectorFactory willThrowException = new ConnectorFactory(config, new ThrowingSecretStore());
+ ConnectorFactory willThrowException = createConnectorFactory(config);
}
@Test(expectedExceptions = IllegalArgumentException.class)
@@ -49,16 +49,15 @@ public class ConnectorFactoryTest {
.enabled(true)
.keyStoreType(PEM)
.keyStorePath("nonEmpty")));
-
- ConnectorFactory willThrowException = new ConnectorFactory(config, new ThrowingSecretStore());
+ ConnectorFactory willThrowException = createConnectorFactory(config);
}
@Test
public void requireThatNoPreBoundChannelWorks() throws Exception {
Server server = new Server();
try {
- ConnectorFactory factory = new ConnectorFactory(new ConnectorConfig(new ConnectorConfig.Builder()),
- new ThrowingSecretStore());
+ ConnectorConfig config = new ConnectorConfig(new ConnectorConfig.Builder());
+ ConnectorFactory factory = createConnectorFactory(config);
JDiscServerConnector connector =
(JDiscServerConnector)factory.createConnector(new DummyMetric(), server, null);
server.addConnector(connector);
@@ -85,8 +84,10 @@ public class ConnectorFactoryTest {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.socket().bind(new InetSocketAddress(0));
- ConnectorFactory factory = new ConnectorFactory(new ConnectorConfig(new ConnectorConfig.Builder()), new ThrowingSecretStore());
- JDiscServerConnector connector = (JDiscServerConnector) factory.createConnector(new DummyMetric(), server, serverChannel);
+ ConnectorConfig config = new ConnectorConfig(new ConnectorConfig.Builder());
+ ConnectorFactory factory = createConnectorFactory(config);
+ JDiscServerConnector connector =
+ (JDiscServerConnector) factory.createConnector(new DummyMetric(), server, serverChannel);
server.addConnector(connector);
server.setHandler(new HelloWorldHandler());
server.start();
@@ -104,6 +105,11 @@ public class ConnectorFactoryTest {
}
}
+ private static ConnectorFactory createConnectorFactory(ConnectorConfig config) {
+ ThrowingSecretStore secretStore = new ThrowingSecretStore();
+ return new ConnectorFactory(config, secretStore, new DefaultSslKeyStoreConfigurator(config, secretStore));
+ }
+
private static class HelloWorldHandler extends AbstractHandler {
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDriver.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDriver.java
index 525cde9d8b3..bcc23facd95 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDriver.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/TestDriver.java
@@ -7,7 +7,7 @@ import com.yahoo.jdisc.application.ContainerBuilder;
import com.yahoo.jdisc.handler.RequestHandler;
import com.yahoo.jdisc.http.ConnectorConfig;
import com.yahoo.jdisc.http.SslContextFactory;
-import com.yahoo.jdisc.http.ssl.jks.JksKeyStore;
+import com.yahoo.jdisc.http.JksKeyStore;
import javax.net.ssl.SSLContext;
import java.io.IOException;