diff options
author | Bjørn Christian Seime <bjorn.christian@seime.no> | 2017-11-16 09:55:03 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-16 09:55:03 +0100 |
commit | 3982fd81899e6a1942b42e1f05dea3ee29e41fb6 (patch) | |
tree | 5af38e4b268ecde2bb89cfdb93683897ea200b70 | |
parent | e429e4e4fa1d5225d2c16d457bebd420f26b0a3b (diff) | |
parent | 26a183be1a9f6bad2d0206449666e3a98a5b7c74 (diff) |
Merge pull request #4132 from vespa-engine/bjorncs/custom-ssl-context
Bjorncs/custom ssl context
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; |