diff options
6 files changed, 244 insertions, 9 deletions
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 33f5edded3c..8e4288d5f5e 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 @@ -11,12 +11,15 @@ import com.yahoo.text.XML; import com.yahoo.vespa.model.container.component.SimpleComponent; import org.w3c.dom.Element; +import java.util.Optional; + import static com.yahoo.component.ComponentSpecification.fromString; import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType; /** * @author Einar M R Rosenvinge * @author bjorncs + * @author mortent */ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig.Producer { @@ -25,14 +28,15 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig private final Element legacyConfig; public ConnectorFactory(String name, int listenPort) { - this(name, listenPort, null, null, null); + this(name, listenPort, null, null, null, null); } public ConnectorFactory(String name, int listenPort, Element legacyConfig, Element sslKeystoreConfigurator, - Element sslTruststoreConfigurator) { + Element sslTruststoreConfigurator, + SimpleComponent sslProviderComponent) { super(new ComponentModel( new BundleInstantiationSpecification(new ComponentId(name), fromString("com.yahoo.jdisc.http.server.jetty.ConnectorFactory"), @@ -40,6 +44,10 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig this.name = name; this.listenPort = listenPort; this.legacyConfig = legacyConfig; + Optional.ofNullable(sslProviderComponent).ifPresent(component -> { + addChild(component); + inject(component); + }); addSslKeyStoreConfigurator(name, sslKeystoreConfigurator); addSslTrustStoreConfigurator(name, sslTruststoreConfigurator); } @@ -153,6 +161,7 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig } } + private void addSslKeyStoreConfigurator(String name, Element sslKeystoreConfigurator) { addSslConfigurator("ssl-keystore-configurator@" + name, DefaultSslKeyStoreConfigurator.class, @@ -172,11 +181,9 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig String bundleName = configuratorElement.getAttribute("bundle"); configuratorComponent = new SimpleComponent(new ComponentModel(idSpec, className, bundleName)); } else { - configuratorComponent = - new SimpleComponent(new ComponentModel(idSpec, defaultImplementation.getName(), "jdisc_http_service")); + configuratorComponent = new SimpleComponent(new ComponentModel(idSpec, defaultImplementation.getName(), "jdisc_http_service")); } addChild(configuratorComponent); inject(configuratorComponent); } - } diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CustomSslProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CustomSslProvider.java new file mode 100644 index 00000000000..46daa2e5c43 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/CustomSslProvider.java @@ -0,0 +1,27 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.model.container.http.ssl; + +import com.yahoo.component.ComponentId; +import com.yahoo.container.bundle.BundleInstantiationSpecification; +import com.yahoo.jdisc.http.ConnectorConfig; +import com.yahoo.osgi.provider.model.ComponentModel; +import com.yahoo.vespa.model.container.component.SimpleComponent; + +import static com.yahoo.component.ComponentSpecification.fromString; + +/** + * @author mortent + */ +public class CustomSslProvider extends SimpleComponent implements ConnectorConfig.Producer { + public CustomSslProvider(String componentId, String className, String bundle) { + super(new ComponentModel( + new BundleInstantiationSpecification(new ComponentId(componentId), + fromString(className), + fromString(bundle)))); + } + + @Override + public void getConfig(ConnectorConfig.Builder builder) { + builder.ssl.enabled(true); + } +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/DefaultSslProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/DefaultSslProvider.java new file mode 100644 index 00000000000..36ab07f977a --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/DefaultSslProvider.java @@ -0,0 +1,62 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.model.container.http.ssl; + +import com.yahoo.component.ComponentId; +import com.yahoo.container.bundle.BundleInstantiationSpecification; +import com.yahoo.jdisc.http.ConnectorConfig; +import com.yahoo.osgi.provider.model.ComponentModel; +import com.yahoo.vespa.model.container.component.SimpleComponent; + +import java.util.Optional; + +import static com.yahoo.component.ComponentSpecification.fromString; + +/** + * @author mortent + */ +public class DefaultSslProvider extends SimpleComponent implements ConnectorConfig.Producer { + public static final String COMPONENT_ID = "default-ssl-provider"; + public static final String COMPONENT_CLASS = "com.yahoo.jdisc.http.ssl.DefaultSslContextFactoryProvider"; + public static final String COMPONENT_BUNDLE = "jdisc_http_service"; + + private final String privateKeyPath; + private final String certificatePath; + private final String caCertificatePath; + private final ConnectorConfig.Ssl.ClientAuth.Enum clientAuthentication; + + public DefaultSslProvider(String privateKeyPath, String certificatePath, String caCertificatePath, String clientAuthentication) { + super(new ComponentModel( + new BundleInstantiationSpecification(new ComponentId(COMPONENT_ID), + fromString(COMPONENT_CLASS), + fromString(COMPONENT_BUNDLE)))); + this.privateKeyPath = privateKeyPath; + this.certificatePath = certificatePath; + this.caCertificatePath = caCertificatePath; + this.clientAuthentication = mapToConfigEnum(clientAuthentication); + } + + @Override + public void getConfig(ConnectorConfig.Builder builder) { + builder.ssl.enabled(true); + builder.ssl.privateKeyFile(privateKeyPath); + builder.ssl.certificateFile(certificatePath); + builder.ssl.caCertificateFile(Optional.ofNullable(caCertificatePath).orElse("")); + builder.ssl.clientAuth(clientAuthentication); + } + + public SimpleComponent getComponent() { + return new SimpleComponent(new ComponentModel(COMPONENT_ID, COMPONENT_CLASS, COMPONENT_BUNDLE)); + } + + private static ConnectorConfig.Ssl.ClientAuth.Enum mapToConfigEnum(String clientAuthValue) { + if ("disabled".equals(clientAuthValue)) { + return ConnectorConfig.Ssl.ClientAuth.Enum.DISABLED; + } else if ("want".equals(clientAuthValue)) { + return ConnectorConfig.Ssl.ClientAuth.Enum.WANT_AUTH; + } else if ("need".equals(clientAuthValue)) { + return ConnectorConfig.Ssl.ClientAuth.Enum.NEED_AUTH; + } else { + return ConnectorConfig.Ssl.ClientAuth.Enum.DISABLED; + } + } +} 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 f88c091cd37..5c15c9bfe7d 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 @@ -5,14 +5,19 @@ import com.yahoo.config.model.builder.xml.XmlHelper; import com.yahoo.config.model.producer.AbstractConfigProducer; import com.yahoo.text.XML; import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder; +import com.yahoo.vespa.model.container.component.SimpleComponent; import com.yahoo.vespa.model.container.http.ConnectorFactory; +import com.yahoo.vespa.model.container.http.ssl.CustomSslProvider; +import com.yahoo.vespa.model.container.http.ssl.DefaultSslProvider; import org.w3c.dom.Element; +import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; /** * @author Einar M R Rosenvinge + * @author mortent */ public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuilder<ConnectorFactory> { private static final Logger log = Logger.getLogger(JettyConnectorBuilder.class.getName()); @@ -34,7 +39,31 @@ public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuil } Element sslKeystoreConfigurator = XML.getChild(serverSpec, "ssl-keystore-configurator"); Element sslTruststoreConfigurator = XML.getChild(serverSpec, "ssl-truststore-configurator"); - return new ConnectorFactory(name, port, legacyServerConfig, sslKeystoreConfigurator, sslTruststoreConfigurator); + SimpleComponent sslProviderComponent = getSslConfigComponents(name, serverSpec); + return new ConnectorFactory(name, port, legacyServerConfig, sslKeystoreConfigurator, sslTruststoreConfigurator, sslProviderComponent); } + SimpleComponent getSslConfigComponents(String serverName, Element serverSpec) { + Element sslConfigurator = XML.getChild(serverSpec, "ssl"); + Element sslProviderConfigurator = XML.getChild(serverSpec, "ssl-provider"); + + if (sslConfigurator != null) { + String privateKeyFile = XML.getValue(XML.getChild(sslConfigurator, "private-key-file")); + String certificateFile = XML.getValue(XML.getChild(sslConfigurator, "certificate-file")); + Optional<String> caCertificateFile = XmlHelper.getOptionalChildValue(sslConfigurator, "ca-certificates-file"); + Optional<String> clientAuthentication = XmlHelper.getOptionalChildValue(sslConfigurator, "client-authentication"); + return new DefaultSslProvider( + privateKeyFile, + certificateFile, + caCertificateFile.orElse(null), + clientAuthentication.orElse(null)); + } else if (sslProviderConfigurator != null) { + String id = sslProviderConfigurator.getAttribute("id"); + String className = sslProviderConfigurator.getAttribute("class"); + String bundle = sslProviderConfigurator.getAttribute("bundle"); + return new CustomSslProvider(id, className, bundle); + } + // No ssl config.. + return null; + } } 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 54c4aabf44c..4f51d4cca7e 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 @@ -12,6 +12,8 @@ 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 com.yahoo.vespa.model.container.http.ssl.DefaultSslProvider; import org.junit.Test; import org.w3c.dom.Element; import org.xml.sax.SAXException; @@ -19,6 +21,7 @@ import org.xml.sax.SAXException; import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.Optional; import java.util.Set; import static com.yahoo.jdisc.http.ConnectorConfig.Ssl.KeyStoreType; @@ -28,9 +31,11 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; /** * @author einarmr + * @author mortent */ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBase { @@ -229,6 +234,87 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas } } + @Test + public void verify_default_ssl_configuration() { + Element clusterElem = DomBuilderTest.parse( + "<jdisc id='default' version='1.0' jetty='true'>\n" + + " <http>\n" + + " <server port='9000' id='minimal'>\n" + + " <ssl>\n" + + " <private-key-file>/foo/key</private-key-file>\n" + + " <certificate-file>/foo/cert</certificate-file>\n" + + " </ssl>\n" + + " </server>\n" + + " <server port='9001' id='with-cacerts'>\n" + + " <ssl>\n" + + " <private-key-file>/foo/key</private-key-file>\n" + + " <certificate-file>/foo/cert</certificate-file>\n" + + " <ca-certificates-file>/foo/cacerts</ca-certificates-file>\n" + + " </ssl>\n" + + " </server>\n" + + " <server port='9002' id='need-client-auth'>\n" + + " <ssl>\n" + + " <private-key-file>/foo/key</private-key-file>\n" + + " <certificate-file>/foo/cert</certificate-file>\n" + + " <client-authentication>need</client-authentication>\n" + + " </ssl>\n" + + " </server>\n" + + " </http>" + + nodesXml + + "\n" + + "</jdisc>"); + + createModel(root, clusterElem); + ConnectorConfig minimalCfg = root.getConfig(ConnectorConfig.class, "default/http/jdisc-jetty/minimal/default-ssl-provider"); + assertTrue(minimalCfg.ssl().enabled()); + assertThat(minimalCfg.ssl().privateKeyFile(), is(equalTo("/foo/key"))); + assertThat(minimalCfg.ssl().certificateFile(), is(equalTo("/foo/cert"))); + assertThat(minimalCfg.ssl().caCertificateFile(), is(equalTo(""))); + assertThat(minimalCfg.ssl().clientAuth(), is(equalTo(ConnectorConfig.Ssl.ClientAuth.Enum.DISABLED))); + + ConnectorConfig withCaCerts = root.getConfig(ConnectorConfig.class, "default/http/jdisc-jetty/with-cacerts/default-ssl-provider"); + assertTrue(withCaCerts.ssl().enabled()); + assertThat(withCaCerts.ssl().privateKeyFile(), is(equalTo("/foo/key"))); + assertThat(withCaCerts.ssl().certificateFile(), is(equalTo("/foo/cert"))); + assertThat(withCaCerts.ssl().caCertificateFile(), is(equalTo("/foo/cacerts"))); + assertThat(withCaCerts.ssl().clientAuth(), is(equalTo(ConnectorConfig.Ssl.ClientAuth.Enum.DISABLED))); + + ConnectorConfig needClientAuth = root.getConfig(ConnectorConfig.class, "default/http/jdisc-jetty/need-client-auth/default-ssl-provider"); + assertTrue(needClientAuth.ssl().enabled()); + assertThat(needClientAuth.ssl().privateKeyFile(), is(equalTo("/foo/key"))); + assertThat(needClientAuth.ssl().certificateFile(), is(equalTo("/foo/cert"))); + assertThat(needClientAuth.ssl().caCertificateFile(), is(equalTo(""))); + assertThat(needClientAuth.ssl().clientAuth(), is(equalTo(ConnectorConfig.Ssl.ClientAuth.Enum.NEED_AUTH))); + + ContainerCluster cluster = (ContainerCluster) root.getChildren().get("default"); + List<ConnectorFactory> connectorFactories = cluster.getChildrenByTypeRecursive(ConnectorFactory.class); + connectorFactories.forEach(connectorFactory -> assertChildComponentExists(connectorFactory, DefaultSslProvider.COMPONENT_ID, DefaultSslProvider.COMPONENT_CLASS)); + } + + @Test + public void verify_ssl_provider_configuration() { + Element clusterElem = DomBuilderTest.parse( + "<jdisc id='default' version='1.0' jetty='true'>\n" + + " <http>\n" + + " <server port='9000' id='ssl'>\n" + + " <ssl-provider id='ssl-provider' class='com.yahoo.CustomSslProvider' bundle='mybundle'/>\n" + + " </server>\n" + + " </http>" + + nodesXml + + "\n" + + "</jdisc>"); + + createModel(root, clusterElem); + ConnectorConfig sslProvider = root.getConfig(ConnectorConfig.class, "default/http/jdisc-jetty/ssl/ssl-provider"); + + assertTrue(sslProvider.ssl().enabled()); + + ContainerCluster cluster = (ContainerCluster) root.getChildren().get("default"); + List<ConnectorFactory> connectorFactories = cluster.getChildrenByTypeRecursive(ConnectorFactory.class); + ConnectorFactory connectorFactory = connectorFactories.get(0); + assertChildComponentExists(connectorFactory, "ssl-provider", "com.yahoo.CustomSslProvider"); + } + private static void assertConnectorHasInjectedComponents(ConnectorFactory connectorFactory, String... componentNames) { Set<String> injectedComponentIds = connectorFactory.getInjectedComponentIds(); assertThat(injectedComponentIds.size(), equalTo(componentNames.length)); @@ -248,6 +334,15 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas assertThat(spec.bundle.toString(), is(bundleName)); } + private static void assertChildComponentExists(ConnectorFactory connectorFactory, String componentId, String className) { + Optional<SimpleComponent> simpleComponent = connectorFactory.getChildren().values().stream() + .map(z -> (SimpleComponent) z) + .filter(component -> component.getComponentId().stringValue().equals(componentId) && + component.getClassId().stringValue().equals(className)) + .findFirst(); + assertTrue(simpleComponent.isPresent()); + } + private void assertJettyServerInConfig() { ContainerCluster cluster = (ContainerCluster) root.getChildren().get("default"); List<JettyHttpServer> jettyServers = cluster.getChildrenByTypeRecursive(JettyHttpServer.class); diff --git a/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def b/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def index 9ae4713c633..157ffabdd63 100644 --- a/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def +++ b/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def @@ -44,6 +44,23 @@ tcpNoDelay bool default=true # Whether to enable SSL for this connector. ssl.enabled bool default=false +# File with private key in PEM format +ssl.privateKeyFile string default="" + +# File with certificate in PEM format +ssl.certificateFile string default="" + +# with trusted CA certificates in PEM format. Used to verify clients +ssl.caCertificateFile string default="" + +# Client authentication mode. See SSLEngine.getNeedClientAuth()/getWantClientAuth() for details. +ssl.clientAuth enum { DISABLED, WANT_AUTH, NEED_AUTH } default=DISABLED + + +######################################################################################### +# Config below is deprecated. Do not use +######################################################################################### + # The name of the key to the password to the key store if in the secret store, if JKS is used. # Must be empty with PEM # By default this is also used to look up the password to the trust store. @@ -89,11 +106,9 @@ ssl.sslKeyManagerFactoryAlgorithm string default="SunX509" # The SSL protocol passed to SSLContext.getInstance() ssl.protocol string default="TLS" -# Client authentication mode. See SSLEngine.getNeedClientAuth()/getWantClientAuth() for details. -ssl.clientAuth enum { DISABLED, WANT_AUTH, NEED_AUTH } default=DISABLED - # The SecureRandom implementation passed to SSLEngine.init() # Java have a default pseudo-random number generator (PRNG) for crypto operations. This default may have performance # issues on some platform (e.g. NativePRNG in Linux utilizes a global lock). Changing the generator to SHA1PRNG may # improve performance. Set value to empty string to use the default generator. ssl.prng string default="" + |