diff options
author | Bjørn Christian Seime <bjorn.christian@seime.no> | 2019-02-25 10:11:08 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-02-25 10:11:08 +0100 |
commit | 6dd9327829f55c32bc75ac005cbe8495efae8ebb (patch) | |
tree | 03ec158960b337e97daa43ce3fb9411a5f3dfb95 | |
parent | 1f5547e1401fda44fa491e93a31c1da15078db42 (diff) | |
parent | d9240123dc87003e688eb1e702d56b722eb647f7 (diff) |
Merge pull request #8572 from vespa-engine/bjorncs/jdisc-mixed-mode
Bjorncs/jdisc mixed mode
18 files changed, 310 insertions, 229 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 73fc9c0cf41..72db12dbbd8 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 @@ -6,7 +6,7 @@ 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 com.yahoo.vespa.model.container.http.ssl.DummySslProvider; +import com.yahoo.vespa.model.container.http.ssl.DefaultSslProvider; import static com.yahoo.component.ComponentSpecification.fromString; @@ -22,7 +22,7 @@ public class ConnectorFactory extends SimpleComponent implements ConnectorConfig private final SimpleComponent sslProviderComponent; public ConnectorFactory(String name, int listenPort) { - this(name, listenPort, new DummySslProvider(name)); + this(name, listenPort, new DefaultSslProvider(name)); } public ConnectorFactory(String name, diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredSslProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredSslProvider.java new file mode 100644 index 00000000000..3c36933c030 --- /dev/null +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/ConfiguredSslProvider.java @@ -0,0 +1,63 @@ +// Copyright 2019 Oath Inc. 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.jdisc.http.ssl.impl.ConfiguredSslContextFactoryProvider; +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 ConfiguredSslProvider extends SimpleComponent implements ConnectorConfig.Producer { + public static final String COMPONENT_ID_PREFIX = "configured-ssl-provider@"; + public static final String COMPONENT_CLASS = ConfiguredSslContextFactoryProvider.class.getName(); + 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 ConfiguredSslProvider(String servername, String privateKeyPath, String certificatePath, String caCertificatePath, String clientAuthentication) { + super(new ComponentModel( + new BundleInstantiationSpecification(new ComponentId(COMPONENT_ID_PREFIX+servername), + 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(getComponentId().stringValue(), 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/ssl/DefaultSslProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/DefaultSslProvider.java index 3554ce95faf..1a5ce615a9d 100644 --- 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 @@ -8,56 +8,24 @@ import com.yahoo.jdisc.http.ssl.impl.DefaultSslContextFactoryProvider; 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 + * @author bjorncs */ public class DefaultSslProvider extends SimpleComponent implements ConnectorConfig.Producer { + public static final String COMPONENT_ID_PREFIX = "default-ssl-provider@"; public static final String COMPONENT_CLASS = DefaultSslContextFactoryProvider.class.getName(); 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 servername, String privateKeyPath, String certificatePath, String caCertificatePath, String clientAuthentication) { + public DefaultSslProvider(String serverName) { super(new ComponentModel( - new BundleInstantiationSpecification(new ComponentId(COMPONENT_ID_PREFIX+servername), + new BundleInstantiationSpecification(new ComponentId(COMPONENT_ID_PREFIX + serverName), 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(getComponentId().stringValue(), 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; - } - } -} + public void getConfig(ConnectorConfig.Builder builder) {} +}
\ No newline at end of file diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/DummySslProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/DummySslProvider.java deleted file mode 100644 index 4e2ee61f33f..00000000000 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/DummySslProvider.java +++ /dev/null @@ -1,31 +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.model.container.http.ssl; - -import com.yahoo.component.ComponentId; -import com.yahoo.container.bundle.BundleInstantiationSpecification; -import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.ssl.ThrowingSslContextFactoryProvider; -import com.yahoo.osgi.provider.model.ComponentModel; -import com.yahoo.vespa.model.container.component.SimpleComponent; - -import static com.yahoo.component.ComponentSpecification.fromString; - -/** - * @author bjorncs - */ -public class DummySslProvider extends SimpleComponent implements ConnectorConfig.Producer { - - public static final String COMPONENT_ID_PREFIX = "dummy-ssl-provider@"; - public static final String COMPONENT_CLASS = ThrowingSslContextFactoryProvider.class.getName(); - public static final String COMPONENT_BUNDLE = "jdisc_http_service"; - - public DummySslProvider(String serverName) { - super(new ComponentModel( - new BundleInstantiationSpecification(new ComponentId(COMPONENT_ID_PREFIX + serverName), - fromString(COMPONENT_CLASS), - fromString(COMPONENT_BUNDLE)))); - } - - @Override - public void getConfig(ConnectorConfig.Builder builder) {} -}
\ No newline at end of file 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 4c41aaa504c..23865eb9bdd 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 @@ -9,8 +9,8 @@ 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.ConfiguredSslProvider; import com.yahoo.vespa.model.container.http.ssl.DefaultSslProvider; -import com.yahoo.vespa.model.container.http.ssl.DummySslProvider; import org.w3c.dom.Element; import java.util.Optional; @@ -39,7 +39,7 @@ public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuil 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( + return new ConfiguredSslProvider( serverName, privateKeyFile, certificateFile, @@ -50,7 +50,7 @@ public class JettyConnectorBuilder extends VespaDomBuilder.DomConfigProducerBuil String bundle = sslProviderConfigurator.getAttribute("bundle"); return new CustomSslProvider(serverName, className, bundle); } else { - return new DummySslProvider(serverName); + return new DefaultSslProvider(serverName); } } } 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 5525b75373d..424f55c67cb 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 @@ -9,7 +9,7 @@ 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 com.yahoo.vespa.model.container.http.ssl.ConfiguredSslProvider; import org.junit.Test; import org.w3c.dom.Element; @@ -150,21 +150,21 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas "</jdisc>"); createModel(root, clusterElem); - ConnectorConfig minimalCfg = root.getConfig(ConnectorConfig.class, "default/http/jdisc-jetty/minimal/default-ssl-provider@minimal"); + ConnectorConfig minimalCfg = root.getConfig(ConnectorConfig.class, "default/http/jdisc-jetty/minimal/configured-ssl-provider@minimal"); 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@with-cacerts"); + ConnectorConfig withCaCerts = root.getConfig(ConnectorConfig.class, "default/http/jdisc-jetty/with-cacerts/configured-ssl-provider@with-cacerts"); 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@need-client-auth"); + ConnectorConfig needClientAuth = root.getConfig(ConnectorConfig.class, "default/http/jdisc-jetty/need-client-auth/configured-ssl-provider@need-client-auth"); assertTrue(needClientAuth.ssl().enabled()); assertThat(needClientAuth.ssl().privateKeyFile(), is(equalTo("/foo/key"))); assertThat(needClientAuth.ssl().certificateFile(), is(equalTo("/foo/cert"))); @@ -173,7 +173,7 @@ public class JettyContainerModelBuilderTest extends ContainerModelBuilderTestBas ContainerCluster cluster = (ContainerCluster) root.getChildren().get("default"); List<ConnectorFactory> connectorFactories = cluster.getChildrenByTypeRecursive(ConnectorFactory.class); - connectorFactories.forEach(connectorFactory -> assertChildComponentExists(connectorFactory, DefaultSslProvider.COMPONENT_CLASS)); + connectorFactories.forEach(connectorFactory -> assertChildComponentExists(connectorFactory, ConfiguredSslProvider.COMPONENT_CLASS)); } @Test diff --git a/jdisc_http_service/abi-spec.json b/jdisc_http_service/abi-spec.json index 099b009fba5..62093a8cf3c 100644 --- a/jdisc_http_service/abi-spec.json +++ b/jdisc_http_service/abi-spec.json @@ -1128,19 +1128,5 @@ "public abstract org.eclipse.jetty.util.ssl.SslContextFactory getInstance(java.lang.String, int)" ], "fields": [] - }, - "com.yahoo.jdisc.http.ssl.ThrowingSslContextFactoryProvider": { - "superClass": "java.lang.Object", - "interfaces": [ - "com.yahoo.jdisc.http.ssl.SslContextFactoryProvider" - ], - "attributes": [ - "public" - ], - "methods": [ - "public void <init>()", - "public org.eclipse.jetty.util.ssl.SslContextFactory getInstance(java.lang.String, int)" - ], - "fields": [] } }
\ No newline at end of file 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 f9892759fbd..5d3550db9d2 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,12 @@ import com.google.inject.Inject; import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.http.ConnectorConfig; import com.yahoo.jdisc.http.ssl.SslContextFactoryProvider; +import com.yahoo.security.tls.TransportSecurityUtils; import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.server.ConnectionFactory; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.OptionalSslConnectionFactory; import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; @@ -15,6 +18,7 @@ import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.util.ssl.SslContextFactory; import java.nio.channels.ServerSocketChannel; +import java.util.List; /** * @author Einar M R Rosenvinge @@ -37,15 +41,8 @@ public class ConnectorFactory { } public ServerConnector createConnector(final Metric metric, final Server server, final ServerSocketChannel ch) { - ServerConnector connector; - if (connectorConfig.ssl().enabled()) { - connector = new JDiscServerConnector(connectorConfig, metric, server, ch, - newSslConnectionFactory(), - newHttpConnectionFactory()); - } else { - connector = new JDiscServerConnector(connectorConfig, metric, server, ch, - newHttpConnectionFactory()); - } + ServerConnector connector = new JDiscServerConnector( + connectorConfig, metric, server, ch, createConnectionFactories().toArray(ConnectionFactory[]::new)); connector.setPort(connectorConfig.listenPort()); connector.setName(connectorConfig.name()); connector.setAcceptQueueSize(connectorConfig.acceptQueueSize()); @@ -55,6 +52,26 @@ public class ConnectorFactory { return connector; } + private List<ConnectionFactory> createConnectionFactories() { + HttpConnectionFactory httpConnectionFactory = newHttpConnectionFactory(); + if (connectorConfig.ssl().enabled()) { + return List.of(newSslConnectionFactory(), httpConnectionFactory); + } else if (TransportSecurityUtils.isTransportSecurityEnabled()) { + SslConnectionFactory sslConnectionsFactory = newSslConnectionFactory(); + switch (TransportSecurityUtils.getInsecureMixedMode()) { + case TLS_CLIENT_MIXED_SERVER: + case PLAINTEXT_CLIENT_MIXED_SERVER: + return List.of(newOptionalSslConnectionFactory(sslConnectionsFactory), sslConnectionsFactory, httpConnectionFactory); + case DISABLED: + return List.of(sslConnectionsFactory, httpConnectionFactory); + default: + throw new IllegalStateException(); + } + } else { + return List.of(httpConnectionFactory); + } + } + private HttpConnectionFactory newHttpConnectionFactory() { HttpConfiguration httpConfig = new HttpConfiguration(); httpConfig.setSendDateHeader(true); @@ -64,7 +81,7 @@ public class ConnectorFactory { httpConfig.setOutputBufferSize(connectorConfig.outputBufferSize()); httpConfig.setRequestHeaderSize(connectorConfig.requestHeaderSize()); httpConfig.setResponseHeaderSize(connectorConfig.responseHeaderSize()); - if (connectorConfig.ssl().enabled()) { + if (connectorConfig.ssl().enabled() || TransportSecurityUtils.isTransportSecurityEnabled()) { // TODO Cleanup once mixed mode is gone httpConfig.addCustomizer(new SecureRequestCustomizer()); } return new HttpConnectionFactory(httpConfig); @@ -75,4 +92,8 @@ public class ConnectorFactory { return new SslConnectionFactory(factory, HttpVersion.HTTP_1_1.asString()); } + private OptionalSslConnectionFactory newOptionalSslConnectionFactory(SslConnectionFactory sslConnectionsFactory) { + return new OptionalSslConnectionFactory(sslConnectionsFactory, HttpVersion.HTTP_1_1.asString()); + } + } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/ThrowingSslContextFactoryProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/ThrowingSslContextFactoryProvider.java deleted file mode 100644 index b28ef54e822..00000000000 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/ThrowingSslContextFactoryProvider.java +++ /dev/null @@ -1,16 +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.jdisc.http.ssl; - -import org.eclipse.jetty.util.ssl.SslContextFactory; - -/** - * A dummy implementation of {@link SslContextFactoryProvider} to be injected into non-ssl connectors - * - * @author bjorncs - */ -public class ThrowingSslContextFactoryProvider implements SslContextFactoryProvider { - @Override - public SslContextFactory getInstance(String containerId, int port) { - throw new UnsupportedOperationException(); - } -}
\ No newline at end of file diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java new file mode 100644 index 00000000000..188fabc4841 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/ConfiguredSslContextFactoryProvider.java @@ -0,0 +1,95 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.ssl.impl; + +import com.yahoo.jdisc.http.ConnectorConfig; +import com.yahoo.jdisc.http.ssl.SslContextFactoryProvider; +import com.yahoo.security.KeyStoreBuilder; +import com.yahoo.security.KeyStoreType; +import com.yahoo.security.KeyUtils; +import com.yahoo.security.X509CertificateUtils; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.List; + +/** + * An implementation of {@link SslContextFactoryProvider} that uses the {@link ConnectorConfig} to construct a {@link SslContextFactory}. + * + * @author bjorncs + */ +public class ConfiguredSslContextFactoryProvider implements SslContextFactoryProvider { + + private final ConnectorConfig connectorConfig; + + public ConfiguredSslContextFactoryProvider(ConnectorConfig connectorConfig) { + validateConfig(connectorConfig.ssl()); + this.connectorConfig = connectorConfig; + } + + @Override + public SslContextFactory getInstance(String containerId, int port) { + ConnectorConfig.Ssl sslConfig = connectorConfig.ssl(); + if (!sslConfig.enabled()) throw new IllegalStateException(); + SslContextFactory factory = new JDiscSslContextFactory(); + + switch (sslConfig.clientAuth()) { + case NEED_AUTH: + factory.setNeedClientAuth(true); + break; + case WANT_AUTH: + factory.setWantClientAuth(true); + break; + } + + // Check if using new ssl syntax from services.xml + factory.setKeyStore(createKeystore(sslConfig)); + factory.setKeyStorePassword(""); + if (!sslConfig.caCertificateFile().isEmpty()) { + factory.setTrustStore(createTruststore(sslConfig)); + } + factory.setProtocol("TLS"); + return factory; + } + + private static void validateConfig(ConnectorConfig.Ssl config) { + if (!config.enabled()) return; + if (config.certificateFile().isEmpty()) { + throw new IllegalArgumentException("Missing certificate file."); + } + if (config.privateKeyFile().isEmpty()) { + throw new IllegalArgumentException("Missing private key file."); + } + + } + + private static KeyStore createTruststore(ConnectorConfig.Ssl sslConfig) { + List<X509Certificate> caCertificates = X509CertificateUtils.certificateListFromPem(readToString(sslConfig.caCertificateFile())); + KeyStoreBuilder truststoreBuilder = KeyStoreBuilder.withType(KeyStoreType.JKS); + for (int i = 0; i < caCertificates.size(); i++) { + truststoreBuilder.withCertificateEntry("entry-" + i, caCertificates.get(i)); + } + return truststoreBuilder.build(); + } + + private static KeyStore createKeystore(ConnectorConfig.Ssl sslConfig) { + PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(readToString(sslConfig.privateKeyFile())); + List<X509Certificate> certificates = X509CertificateUtils.certificateListFromPem(readToString(sslConfig.certificateFile())); + return KeyStoreBuilder.withType(KeyStoreType.JKS).withKeyEntry("default", privateKey, certificates).build(); + } + + private static String readToString(String filename) { + try { + return Files.readString(Paths.get(filename), StandardCharsets.UTF_8); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/DefaultSslContextFactoryProvider.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/DefaultSslContextFactoryProvider.java index f9cdefeb5e8..03796c551e5 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/DefaultSslContextFactoryProvider.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/DefaultSslContextFactoryProvider.java @@ -1,95 +1,35 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.jdisc.http.ssl.impl; -import com.yahoo.jdisc.http.ConnectorConfig; +import com.yahoo.component.AbstractComponent; import com.yahoo.jdisc.http.ssl.SslContextFactoryProvider; -import com.yahoo.security.KeyStoreBuilder; -import com.yahoo.security.KeyStoreType; -import com.yahoo.security.KeyUtils; -import com.yahoo.security.X509CertificateUtils; +import com.yahoo.security.tls.ReloadingTlsContext; +import com.yahoo.security.tls.TlsContext; +import com.yahoo.security.tls.TransportSecurityUtils; import org.eclipse.jetty.util.ssl.SslContextFactory; -import java.io.IOException; -import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.cert.X509Certificate; -import java.util.List; - /** - * JDisc's default implementation of {@link SslContextFactoryProvider} that uses the {@link ConnectorConfig} to construct a {@link SslContextFactory}. + * The default implementation of {@link SslContextFactoryProvider} to be injected into connectors without explicit ssl configuration * * @author bjorncs */ -public class DefaultSslContextFactoryProvider implements SslContextFactoryProvider { - - private final ConnectorConfig connectorConfig; +public class DefaultSslContextFactoryProvider extends AbstractComponent implements SslContextFactoryProvider { - public DefaultSslContextFactoryProvider(ConnectorConfig connectorConfig) { - validateConfig(connectorConfig.ssl()); - this.connectorConfig = connectorConfig; - } + private final TlsContext tlsContext = TransportSecurityUtils.getConfigFile() + .map(configFile -> new ReloadingTlsContext(configFile, TransportSecurityUtils.getInsecureAuthorizationMode())) + .orElse(null); @Override public SslContextFactory getInstance(String containerId, int port) { - ConnectorConfig.Ssl sslConfig = connectorConfig.ssl(); - if (!sslConfig.enabled()) throw new IllegalStateException(); - SslContextFactory factory = new JDiscSslContextFactory(); - - switch (sslConfig.clientAuth()) { - case NEED_AUTH: - factory.setNeedClientAuth(true); - break; - case WANT_AUTH: - factory.setWantClientAuth(true); - break; - } - - // Check if using new ssl syntax from services.xml - factory.setKeyStore(createKeystore(sslConfig)); - factory.setKeyStorePassword(""); - if (!sslConfig.caCertificateFile().isEmpty()) { - factory.setTrustStore(createTruststore(sslConfig)); + if (tlsContext != null) { + return new TlsContextManagedSslContextFactory(tlsContext); + } else { + throw new UnsupportedOperationException(); } - factory.setProtocol("TLS"); - factory.setEndpointIdentificationAlgorithm(null); // disable hostname verification of client certs - return factory; } - private static void validateConfig(ConnectorConfig.Ssl config) { - if (!config.enabled()) return; - if (config.certificateFile().isEmpty()) { - throw new IllegalArgumentException("Missing certificate file."); - } - if (config.privateKeyFile().isEmpty()) { - throw new IllegalArgumentException("Missing private key file."); - } - - } - - private static KeyStore createTruststore(ConnectorConfig.Ssl sslConfig) { - List<X509Certificate> caCertificates = X509CertificateUtils.certificateListFromPem(readToString(sslConfig.caCertificateFile())); - KeyStoreBuilder truststoreBuilder = KeyStoreBuilder.withType(KeyStoreType.JKS); - for (int i = 0; i < caCertificates.size(); i++) { - truststoreBuilder.withCertificateEntry("entry-" + i, caCertificates.get(i)); - } - return truststoreBuilder.build(); - } - - private static KeyStore createKeystore(ConnectorConfig.Ssl sslConfig) { - PrivateKey privateKey = KeyUtils.fromPemEncodedPrivateKey(readToString(sslConfig.privateKeyFile())); - List<X509Certificate> certificates = X509CertificateUtils.certificateListFromPem(readToString(sslConfig.certificateFile())); - return KeyStoreBuilder.withType(KeyStoreType.JKS).withKeyEntry("default", privateKey, certificates).build(); - } - - private static String readToString(String filename) { - try { - return new String(Files.readAllBytes(Paths.get(filename))); - } catch (IOException e) { - throw new UncheckedIOException(e); - } + @Override + public void deconstruct() { + tlsContext.close(); } - -} +}
\ No newline at end of file diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/TlsContextManagedSslContextFactory.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/TlsContextManagedSslContextFactory.java new file mode 100644 index 00000000000..d7d28e7d242 --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/ssl/impl/TlsContextManagedSslContextFactory.java @@ -0,0 +1,42 @@ +// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.ssl.impl; + +import com.yahoo.security.tls.TlsContext; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import javax.net.ssl.SSLEngine; +import java.net.InetSocketAddress; + +/** + * A Jetty {@link SslContextFactory} backed by {@link TlsContext}. + * Overrides methods that are used by Jetty to construct ssl sockets and ssl engines. + * + * @author bjorncs + */ +class TlsContextManagedSslContextFactory extends SslContextFactory { + + private final TlsContext tlsContext; + + TlsContextManagedSslContextFactory(TlsContext tlsContext) { + this.tlsContext = tlsContext; + } + + @Override protected void doStart() { } // Override default behaviour + @Override protected void doStop() { } // Override default behaviour + + @Override + public SSLEngine newSSLEngine() { + return tlsContext.createSslEngine(); + } + + @Override + public SSLEngine newSSLEngine(InetSocketAddress address) { + return tlsContext.createSslEngine(address.getHostString(), address.getPort()); + } + + @Override + public SSLEngine newSSLEngine(String host, int port) { + return tlsContext.createSslEngine(host, port); + } + +} 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 a4baccb86c9..cc2a00c08c6 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 @@ -10,7 +10,7 @@ import com.yahoo.jdisc.http.ConnectorConfig; import com.yahoo.jdisc.http.ConnectorConfig.Builder; import com.yahoo.jdisc.http.server.jetty.ConnectorFactory; -import com.yahoo.jdisc.http.ssl.impl.DefaultSslContextFactoryProvider; +import com.yahoo.jdisc.http.ssl.impl.ConfiguredSslContextFactoryProvider; /** * Guice module for test ConnectorFactories @@ -46,7 +46,7 @@ public class ConnectorFactoryRegistryModule implements Module { private static class StaticKeyDbConnectorFactory extends ConnectorFactory { public StaticKeyDbConnectorFactory(ConnectorConfig connectorConfig) { - super(connectorConfig, new DefaultSslContextFactoryProvider(connectorConfig)); + super(connectorConfig, new ConfiguredSslContextFactoryProvider(connectorConfig)); } } 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 cf32801ce88..3a932ba6113 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 @@ -3,7 +3,7 @@ package com.yahoo.jdisc.http.server.jetty; import com.yahoo.jdisc.Metric; import com.yahoo.jdisc.http.ConnectorConfig; -import com.yahoo.jdisc.http.ssl.impl.DefaultSslContextFactoryProvider; +import com.yahoo.jdisc.http.ssl.impl.ConfiguredSslContextFactoryProvider; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; @@ -78,7 +78,7 @@ public class ConnectorFactoryTest { } private static ConnectorFactory createConnectorFactory(ConnectorConfig config) { - return new ConnectorFactory(config, new DefaultSslContextFactoryProvider(config)); + return new ConnectorFactory(config, new ConfiguredSslContextFactoryProvider(config)); } private static class HelloWorldHandler extends AbstractHandler { diff --git a/security-utils/src/main/java/com/yahoo/security/tls/DefaultTlsContext.java b/security-utils/src/main/java/com/yahoo/security/tls/DefaultTlsContext.java index c9c326df9ed..e74ad49b2f5 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/DefaultTlsContext.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/DefaultTlsContext.java @@ -2,7 +2,7 @@ package com.yahoo.security.tls; import com.yahoo.security.SslContextBuilder; -import com.yahoo.security.tls.authz.PeerAuthorizerTrustManagersFactory; +import com.yahoo.security.tls.authz.PeerAuthorizerTrustManager; import com.yahoo.security.tls.policy.AuthorizedPeers; import javax.net.ssl.SSLContext; @@ -12,6 +12,7 @@ import java.security.PrivateKey; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.List; +import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -133,7 +134,9 @@ public class DefaultTlsContext implements TlsContext { builder.withTrustStore(caCertificates); } if (authorizedPeers != null) { - builder.withTrustManagerFactory(new PeerAuthorizerTrustManagersFactory(authorizedPeers, mode)); + builder.withTrustManagerFactory(truststore -> new PeerAuthorizerTrustManager(authorizedPeers, mode, truststore)); + } else { + builder.withTrustManagerFactory(truststore -> new PeerAuthorizerTrustManager(new AuthorizedPeers(Set.of()), AuthorizationMode.DISABLE, truststore)); } return builder.build(); } diff --git a/security-utils/src/main/java/com/yahoo/security/tls/ReloadingTlsContext.java b/security-utils/src/main/java/com/yahoo/security/tls/ReloadingTlsContext.java index b57105f54f9..f1fc62de56a 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/ReloadingTlsContext.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/ReloadingTlsContext.java @@ -7,6 +7,7 @@ import com.yahoo.security.KeyUtils; import com.yahoo.security.SslContextBuilder; import com.yahoo.security.X509CertificateUtils; import com.yahoo.security.tls.authz.PeerAuthorizerTrustManager; +import com.yahoo.security.tls.policy.AuthorizedPeers; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; @@ -20,6 +21,7 @@ import java.security.KeyStore; import java.security.cert.X509Certificate; import java.time.Duration; import java.util.List; +import java.util.Set; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -110,7 +112,7 @@ public class ReloadingTlsContext implements TlsContext { .withTrustManagerFactory( ignoredTruststore -> options.getAuthorizedPeers() .map(authorizedPeers -> (X509ExtendedTrustManager) new PeerAuthorizerTrustManager(authorizedPeers, mode, mutableTrustManager)) - .orElse(mutableTrustManager)) + .orElseGet(() -> new PeerAuthorizerTrustManager(new AuthorizedPeers(Set.of()), AuthorizationMode.DISABLE, mutableTrustManager))) .build(); return new DefaultTlsContext(sslContext, options.getAcceptedCiphers()); } diff --git a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java index eee2e502183..3ddd0861f39 100644 --- a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java +++ b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManager.java @@ -7,11 +7,14 @@ import com.yahoo.security.tls.TrustManagerUtils; import com.yahoo.security.tls.policy.AuthorizedPeers; import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSocket; import javax.net.ssl.X509ExtendedTrustManager; import java.net.Socket; import java.security.KeyStore; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.Objects; import java.util.Optional; import java.util.logging.Logger; @@ -55,24 +58,28 @@ public class PeerAuthorizerTrustManager extends X509ExtendedTrustManager { @Override public void checkClientTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + overrideHostnameVerification(socket); defaultTrustManager.checkClientTrusted(chain, authType, socket); authorizePeer(chain[0], authType, true, null); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, Socket socket) throws CertificateException { + overrideHostnameVerification(socket); defaultTrustManager.checkServerTrusted(chain, authType, socket); authorizePeer(chain[0], authType, false, null); } @Override public void checkClientTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException { + overrideHostnameVerification(sslEngine); defaultTrustManager.checkClientTrusted(chain, authType, sslEngine); authorizePeer(chain[0], authType, true, sslEngine); } @Override public void checkServerTrusted(X509Certificate[] chain, String authType, SSLEngine sslEngine) throws CertificateException { + overrideHostnameVerification(sslEngine); defaultTrustManager.checkServerTrusted(chain, authType, sslEngine); authorizePeer(chain[0], authType, false, sslEngine); } @@ -114,4 +121,31 @@ public class PeerAuthorizerTrustManager extends X509ExtendedTrustManager { certificate.getSubjectX500Principal(), X509CertificateUtils.getSubjectAlternativeNames(certificate), authType, isVerifyingClient); } + private static void overrideHostnameVerification(SSLEngine engine) { + SSLParameters params = engine.getSSLParameters(); + if (overrideHostnameVerification(params)) { + engine.setSSLParameters(params); + } + } + + private static void overrideHostnameVerification(Socket socket) { + if (socket instanceof SSLSocket) { + SSLSocket sslSocket = (SSLSocket) socket; + SSLParameters params = sslSocket.getSSLParameters(); + if (overrideHostnameVerification(params)) { + sslSocket.setSSLParameters(params); + } + } + } + + // Disable the default hostname verification that is performed by underlying trust manager when 'HTTPS' is used as endpoint identification algorithm. + // Some http clients, notably the new http client in Java 11, does not allow user configuration of the endpoint algorithm or custom HostnameVerifier. + private static boolean overrideHostnameVerification(SSLParameters params) { + if (Objects.equals("HTTPS", params.getEndpointIdentificationAlgorithm())) { + params.setEndpointIdentificationAlgorithm(""); + return true; + } + return false; + } + } diff --git a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManagersFactory.java b/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManagersFactory.java deleted file mode 100644 index 6ec8450c035..00000000000 --- a/security-utils/src/main/java/com/yahoo/security/tls/authz/PeerAuthorizerTrustManagersFactory.java +++ /dev/null @@ -1,26 +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.security.tls.authz; - -import com.yahoo.security.SslContextBuilder; -import com.yahoo.security.tls.AuthorizationMode; -import com.yahoo.security.tls.policy.AuthorizedPeers; - -import java.security.KeyStore; - -/** - * @author bjorncs - */ -public class PeerAuthorizerTrustManagersFactory implements SslContextBuilder.TrustManagerFactory { - private final AuthorizedPeers authorizedPeers; - private AuthorizationMode mode; - - public PeerAuthorizerTrustManagersFactory(AuthorizedPeers authorizedPeers, AuthorizationMode mode) { - this.authorizedPeers = authorizedPeers; - this.mode = mode; - } - - @Override - public PeerAuthorizerTrustManager createTrustManager(KeyStore truststore) { - return new PeerAuthorizerTrustManager(authorizedPeers, mode, truststore); - } -} |