From 777dba2f7f26ba7cda4dc43453177d244b1c6331 Mon Sep 17 00:00:00 2001 From: Artem Prigoda Date: Sun, 25 Mar 2018 21:52:27 +0200 Subject: [PATCH] Add TLS socket logging appender Add support for sending logs to a server that requires a secure means of communication via SSL/TLS. This change adds a new logging factory which produces a new socket factory for creating secure client SSL/TLS sockets. * The default configuration allows to communicate with a server with a certificate signed by a CA. In that case the administrator only need to change the port and the type of the appender. * If the server is deployed in a internal network with a self-signed certificate, the factory allows to specify a path to a trust store with the server certificate. * If the server requires the client to authenticate itself, the factory allows to specify a path to a key store with a client certificate that will be sent to the server during negotiation. * The factory allows to configure allowed cipher suites and SSL/TLS protocols. --- .../logging/TlsSocketAppenderFactory.java | 429 ++++++++++++++++++ .../io.dropwizard.logging.AppenderFactory | 1 + .../java/io/dropwizard/logging/TcpServer.java | 83 ++++ .../logging/TcpSocketAppenderFactoryTest.java | 66 +-- .../logging/TlsSocketAppenderFactoryTest.java | 108 +++++ .../src/test/resources/stores/tls_client.jks | Bin 0 -> 958 bytes .../src/test/resources/stores/tls_server.jks | Bin 0 -> 2248 bytes .../resources/yaml/logging-tls-custom.yml | 20 + .../src/test/resources/yaml/logging-tls.yml | 7 + 9 files changed, 661 insertions(+), 53 deletions(-) create mode 100644 dropwizard-logging/src/main/java/io/dropwizard/logging/TlsSocketAppenderFactory.java create mode 100644 dropwizard-logging/src/test/java/io/dropwizard/logging/TcpServer.java create mode 100644 dropwizard-logging/src/test/java/io/dropwizard/logging/TlsSocketAppenderFactoryTest.java create mode 100644 dropwizard-logging/src/test/resources/stores/tls_client.jks create mode 100644 dropwizard-logging/src/test/resources/stores/tls_server.jks create mode 100644 dropwizard-logging/src/test/resources/yaml/logging-tls-custom.yml create mode 100644 dropwizard-logging/src/test/resources/yaml/logging-tls.yml diff --git a/dropwizard-logging/src/main/java/io/dropwizard/logging/TlsSocketAppenderFactory.java b/dropwizard-logging/src/main/java/io/dropwizard/logging/TlsSocketAppenderFactory.java new file mode 100644 index 00000000000..5220c07aebd --- /dev/null +++ b/dropwizard-logging/src/main/java/io/dropwizard/logging/TlsSocketAppenderFactory.java @@ -0,0 +1,429 @@ +package io.dropwizard.logging; + +import ch.qos.logback.core.spi.DeferredProcessingAware; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonTypeName; +import com.google.common.collect.Iterables; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.annotation.Nullable; +import javax.net.SocketFactory; +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.util.List; + +/** + * An {@link AppenderFactory} implementation which provides an appender that writes events to a TCP socket + * secured by the TLS/SSL protocol on the presentation layer. + *

+ * Configuration Parameters: + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
NameDefaultDescription
{@code keyStorePath}(none) + * The path to the Java key store which contains the host certificate and private key. + *
{@code keyStorePassword}(none) + * The password used to access the key store. + *
{@code keyStoreType}{@code JKS} + * The type of key store (usually {@code JKS}, {@code PKCS12}, {@code JCEKS}, + * {@code Windows-MY}, or {@code Windows-ROOT}). + *
{@code keyStoreProvider}(none) + * The JCE provider to use to access the key store. + *
{@code trustStorePath}(none) + * The path to the Java key store which contains the CA certificates used to establish + * trust. + *
{@code trustStorePassword}(none)The password used to access the trust store.
{@code trustStoreType}{@code JKS} + * The type of trust store (usually {@code JKS}, {@code PKCS12}, {@code JCEKS}, + * {@code Windows-MY}, or {@code Windows-ROOT}). + *
{@code trustStoreProvider}(none) + * The JCE provider to use to access the trust store. + *
{@code jceProvider}(none)The name of the JCE provider to use for cryptographic support.
{@code validateCerts}false + * Whether or not to validate TLS certificates before starting. If enabled, Dropwizard + * will refuse to start with expired or otherwise invalid certificates. + *
{@code validatePeers}false + * Whether or not to validate TLS peer certificates. + *
{@code supportedProtocols}JVM default + * A list of protocols (e.g., {@code SSLv3}, {@code TLSv1}) which are supported. All + * other protocols will be refused. + *
{@code excludedProtocols}[SSL, SSLv2, SSLv2Hello, SSLv3] + * A list of protocols (e.g., {@code SSLv3}, {@code TLSv1}) which are excluded. These + * protocols will be refused. + *
{@code supportedCipherSuites}JVM default + * A list of cipher suites (e.g., {@code TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}) which + * are supported. All other cipher suites will be refused + *
{@code excludedCipherSuites}[.*_(MD5|SHA|SHA1)$] + * A list of cipher suites (e.g., {@code TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}) which + * are excluded. These cipher suites will be refused. + *
+ *

+ * For more configuration parameters, see {@link TcpSocketAppenderFactory}. + * + * @see TcpSocketAppenderFactory + */ +@JsonTypeName("tls") +public class TlsSocketAppenderFactory extends TcpSocketAppenderFactory { + + @Nullable + private String keyStorePath; + @Nullable + private String keyStorePassword; + @NotEmpty + private String keyStoreType = "JKS"; + @Nullable + private String keyStoreProvider; + + @Nullable + private String trustStorePath; + @Nullable + private String trustStorePassword; + @NotEmpty + private String trustStoreType = "JKS"; + @Nullable + private String trustStoreProvider; + + @Nullable + private String jceProvider; + + @Nullable + private List supportedProtocols; + @Nullable + private List excludedProtocols; + + @Nullable + private List supportedCipherSuites; + @Nullable + private List excludedCipherSuites; + + private boolean validateCerts; + private boolean validatePeers; + + @JsonProperty + public boolean isValidatePeers() { + return validatePeers; + } + + @JsonProperty + public void setValidatePeers(boolean validatePeers) { + this.validatePeers = validatePeers; + } + + @JsonProperty + public boolean isValidateCerts() { + return validateCerts; + } + + @JsonProperty + public void setValidateCerts(boolean validateCerts) { + this.validateCerts = validateCerts; + } + + @JsonProperty + @Nullable + public List getExcludedCipherSuites() { + return excludedCipherSuites; + } + + @JsonProperty + public void setExcludedCipherSuites(List excludedCipherSuites) { + this.excludedCipherSuites = excludedCipherSuites; + } + + @JsonProperty + @Nullable + public List getSupportedCipherSuites() { + return supportedCipherSuites; + } + + @JsonProperty + public void setSupportedCipherSuites(List supportedCipherSuites) { + this.supportedCipherSuites = supportedCipherSuites; + } + + @JsonProperty + @Nullable + public List getExcludedProtocols() { + return excludedProtocols; + } + + @JsonProperty + public void setExcludedProtocols(List excludedProtocols) { + this.excludedProtocols = excludedProtocols; + } + + @JsonProperty + @Nullable + public List getSupportedProtocols() { + return supportedProtocols; + } + + @JsonProperty + public void setSupportedProtocols(List supportedProtocols) { + this.supportedProtocols = supportedProtocols; + } + + @JsonProperty + @Nullable + public String getTrustStoreProvider() { + return trustStoreProvider; + } + + @JsonProperty + public void setTrustStoreProvider(String trustStoreProvider) { + this.trustStoreProvider = trustStoreProvider; + } + + @JsonProperty + @Nullable + public String getTrustStoreType() { + return trustStoreType; + } + + @JsonProperty + public void setTrustStoreType(String trustStoreType) { + this.trustStoreType = trustStoreType; + } + + @JsonProperty + @Nullable + public String getTrustStorePassword() { + return trustStorePassword; + } + + @JsonProperty + public void setTrustStorePassword(String trustStorePassword) { + this.trustStorePassword = trustStorePassword; + } + + @JsonProperty + @Nullable + public String getTrustStorePath() { + return trustStorePath; + } + + @JsonProperty + public void setTrustStorePath(String trustStorePath) { + this.trustStorePath = trustStorePath; + } + + @JsonProperty + @Nullable + public String getKeyStoreProvider() { + return keyStoreProvider; + } + + @JsonProperty + public void setKeyStoreProvider(String keyStoreProvider) { + this.keyStoreProvider = keyStoreProvider; + } + + @JsonProperty + @Nullable + public String getKeyStoreType() { + return keyStoreType; + } + + @JsonProperty + public void setKeyStoreType(String keyStoreType) { + this.keyStoreType = keyStoreType; + } + + @JsonProperty + @Nullable + public String getKeyStorePassword() { + return keyStorePassword; + } + + @JsonProperty + public void setKeyStorePassword(String keyStorePassword) { + this.keyStorePassword = keyStorePassword; + } + + @JsonProperty + @Nullable + public String getKeyStorePath() { + return keyStorePath; + } + + @JsonProperty + public void setKeyStorePath(String keyStorePath) { + this.keyStorePath = keyStorePath; + } + + @JsonProperty + @Nullable + public String getJceProvider() { + return jceProvider; + } + + @JsonProperty + public void setJceProvider(String jceProvider) { + this.jceProvider = jceProvider; + } + + private SslContextFactory createSslContextFactory() { + SslContextFactory factory = new SslContextFactory(); + if (keyStorePath != null) { + factory.setKeyStorePath(keyStorePath); + } + factory.setKeyStoreType(keyStoreType); + if (keyStorePassword != null) { + factory.setKeyStorePassword(keyStorePassword); + } + if (keyStoreProvider != null) { + factory.setKeyStoreProvider(keyStoreProvider); + } + if (trustStorePath != null) { + factory.setTrustStorePath(trustStorePath); + } + if (trustStorePassword != null) { + factory.setTrustStorePassword(trustStorePassword); + } + factory.setTrustStoreType(trustStoreType); + if (trustStoreProvider != null) { + factory.setTrustStoreProvider(trustStoreProvider); + } + factory.setValidateCerts(validateCerts); + factory.setValidatePeerCerts(validatePeers); + if (supportedProtocols != null) { + factory.setIncludeProtocols(Iterables.toArray(supportedProtocols, String.class)); + } + if (excludedProtocols != null) { + factory.setExcludeProtocols(Iterables.toArray(excludedProtocols, String.class)); + } + if (supportedCipherSuites != null) { + factory.setIncludeCipherSuites(Iterables.toArray(supportedCipherSuites, String.class)); + } + if (excludedCipherSuites != null) { + factory.setExcludeCipherSuites(Iterables.toArray(excludedCipherSuites, String.class)); + } + if (jceProvider != null) { + factory.setProvider(jceProvider); + } + return factory; + } + + @Override + protected SocketFactory socketFactory() { + final SslContextFactory sslContextFactory = createSslContextFactory(); + try { + sslContextFactory.start(); + } catch (Exception e) { + throw new IllegalStateException("Unable to configure SSLContext", e); + } + // We use an adapter over the `newSslSocket` call of Jetty's `SslContextFactory`, because it provides more + // advanced socket configuration than Java's `SSLSocketFactory`. + return new SocketFactory() { + @Override + public Socket createSocket() throws IOException { + return sslContextFactory.newSslSocket(); + } + + @Override + public Socket createSocket(String host, int port) throws IOException, UnknownHostException { + return unsupported(); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException { + return unsupported(); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return unsupported(); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return unsupported(); + } + + private Socket unsupported() { + throw new UnsupportedOperationException("Only createSocket is supported"); + } + }; + } + + +} diff --git a/dropwizard-logging/src/main/resources/META-INF/services/io.dropwizard.logging.AppenderFactory b/dropwizard-logging/src/main/resources/META-INF/services/io.dropwizard.logging.AppenderFactory index fa2a7206b5a..639421efa50 100644 --- a/dropwizard-logging/src/main/resources/META-INF/services/io.dropwizard.logging.AppenderFactory +++ b/dropwizard-logging/src/main/resources/META-INF/services/io.dropwizard.logging.AppenderFactory @@ -3,3 +3,4 @@ io.dropwizard.logging.FileAppenderFactory io.dropwizard.logging.SyslogAppenderFactory io.dropwizard.logging.TcpSocketAppenderFactory io.dropwizard.logging.UdpSocketAppenderFactory +io.dropwizard.logging.TlsSocketAppenderFactory diff --git a/dropwizard-logging/src/test/java/io/dropwizard/logging/TcpServer.java b/dropwizard-logging/src/test/java/io/dropwizard/logging/TcpServer.java new file mode 100644 index 00000000000..5db38efa75c --- /dev/null +++ b/dropwizard-logging/src/test/java/io/dropwizard/logging/TcpServer.java @@ -0,0 +1,83 @@ +package io.dropwizard.logging; + +import org.junit.rules.ExternalResource; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CountDownLatch; + +import static org.assertj.core.api.Assertions.assertThat; + +class TcpServer extends ExternalResource { + + private final Thread thread; + private final ServerSocket serverSocket; + private final int messageCount = 100; + private final CountDownLatch latch = new CountDownLatch(messageCount); + + TcpServer(ServerSocket serverSocket) { + this.serverSocket = serverSocket; + thread = new Thread(() -> { + while (!Thread.currentThread().isInterrupted()) { + Socket socket; + try { + socket = serverSocket.accept(); + } catch (SocketException e) { + break; + } catch (IOException e) { + e.printStackTrace(); + continue; + } + new Thread(() -> readAndVerifyData(socket)).start(); + } + }); + } + + ServerSocket getServerSocket() { + return serverSocket; + } + + int getMessageCount() { + return messageCount; + } + + CountDownLatch getLatch() { + return latch; + } + + @Override + protected void before() throws Throwable { + thread.start(); + } + + @Override + protected void after() { + thread.interrupt(); + try { + serverSocket.close(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + private void readAndVerifyData(Socket socket) { + try (Socket s = socket; BufferedReader reader = new BufferedReader(new InputStreamReader( + s.getInputStream(), StandardCharsets.UTF_8))) { + for (int i = 0; i < messageCount; i++) { + String line = reader.readLine(); + if (line == null) { + break; + } + assertThat(line).startsWith("INFO").contains("com.example.app: Application log " + i); + latch.countDown(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/dropwizard-logging/src/test/java/io/dropwizard/logging/TcpSocketAppenderFactoryTest.java b/dropwizard-logging/src/test/java/io/dropwizard/logging/TcpSocketAppenderFactoryTest.java index 5212113f437..e4fc94b32d5 100644 --- a/dropwizard-logging/src/test/java/io/dropwizard/logging/TcpSocketAppenderFactoryTest.java +++ b/dropwizard-logging/src/test/java/io/dropwizard/logging/TcpSocketAppenderFactoryTest.java @@ -9,22 +9,16 @@ import io.dropwizard.util.Duration; import io.dropwizard.util.Size; import io.dropwizard.validation.BaseValidator; -import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.io.InputStreamReader; import java.net.ServerSocket; -import java.net.Socket; -import java.net.SocketException; import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; @@ -32,11 +26,8 @@ public class TcpSocketAppenderFactoryTest { private static final int TCP_PORT = 24562; - private Thread thread; - private ServerSocket ss; - - private int messageCount = 100; - private CountDownLatch latch = new CountDownLatch(messageCount); + @Rule + public TcpServer tcpServer = new TcpServer(createServerSocket()); private ObjectMapper objectMapper = Jackson.newObjectMapper(); private YamlConfigurationFactory yamlConfigurationFactory = new YamlConfigurationFactory<>( @@ -45,47 +36,16 @@ public class TcpSocketAppenderFactoryTest { @Before public void setUp() throws Exception { objectMapper.getSubtypeResolver().registerSubtypes(TcpSocketAppenderFactory.class); - ss = new ServerSocket(TCP_PORT); - thread = new Thread(() -> { - while (!Thread.currentThread().isInterrupted()) { - Socket socket; - try { - socket = ss.accept(); - } catch (SocketException e) { - break; - } catch (IOException e) { - e.printStackTrace(); - continue; - } - new Thread(() -> readAndVerifyData(socket)).start(); - } - }); - thread.start(); } - private void readAndVerifyData(Socket socket) { - try (Socket s = socket; BufferedReader reader = new BufferedReader(new InputStreamReader( - s.getInputStream(), StandardCharsets.UTF_8))) { - for (int i = 0; i < messageCount; i++) { - String line = reader.readLine(); - if (line == null) { - break; - } - assertThat(line).startsWith("INFO").contains("com.example.app: Application log " + i); - latch.countDown(); - } + private ServerSocket createServerSocket() { + try { + return new ServerSocket(TCP_PORT); } catch (IOException e) { - e.printStackTrace(); + throw new IllegalStateException(e); } } - - @After - public void tearDown() throws Exception { - thread.interrupt(); - ss.close(); - } - private static File resourcePath(String path) throws URISyntaxException { return new File(Resources.getResource(path).toURI()); } @@ -109,12 +69,12 @@ public void testTestTcpLogging() throws Exception { loggingFactory.configure(new MetricRegistry(), "tcp-test"); Logger logger = LoggerFactory.getLogger("com.example.app"); - for (int i = 0; i < messageCount; i++) { + for (int i = 0; i < tcpServer.getMessageCount(); i++) { logger.info("Application log {}", i); } - latch.await(5, TimeUnit.SECONDS); - assertThat(latch.getCount()).isEqualTo(0); + tcpServer.getLatch().await(5, TimeUnit.SECONDS); + assertThat(tcpServer.getLatch().getCount()).isEqualTo(0); loggingFactory.reset(); } @@ -125,13 +85,13 @@ public void testBufferingTcpLogging() throws Exception { loggingFactory.configure(new MetricRegistry(), "tcp-test"); Logger logger = LoggerFactory.getLogger("com.example.app"); - for (int i = 0; i < messageCount; i++) { + for (int i = 0; i < tcpServer.getMessageCount(); i++) { logger.info("Application log {}", i); } // We have to flush the buffer manually loggingFactory.reset(); - latch.await(5, TimeUnit.SECONDS); - assertThat(latch.getCount()).isEqualTo(0); + tcpServer.getLatch().await(5, TimeUnit.SECONDS); + assertThat(tcpServer.getLatch().getCount()).isEqualTo(0); } } diff --git a/dropwizard-logging/src/test/java/io/dropwizard/logging/TlsSocketAppenderFactoryTest.java b/dropwizard-logging/src/test/java/io/dropwizard/logging/TlsSocketAppenderFactoryTest.java new file mode 100644 index 00000000000..d8021e47ac8 --- /dev/null +++ b/dropwizard-logging/src/test/java/io/dropwizard/logging/TlsSocketAppenderFactoryTest.java @@ -0,0 +1,108 @@ +package io.dropwizard.logging; + +import ch.qos.logback.classic.spi.ILoggingEvent; +import com.codahale.metrics.MetricRegistry; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.google.common.io.Resources; +import io.dropwizard.configuration.ResourceConfigurationSourceProvider; +import io.dropwizard.configuration.SubstitutingSourceProvider; +import io.dropwizard.configuration.YamlConfigurationFactory; +import io.dropwizard.jackson.Jackson; +import io.dropwizard.validation.BaseValidator; +import org.apache.commons.text.StrSubstitutor; +import org.eclipse.jetty.util.ssl.SslContextFactory; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.net.ServerSocket; +import java.net.URISyntaxException; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThat; + +public class TlsSocketAppenderFactoryTest { + + @Rule + public TcpServer tcpServer = new TcpServer(createServerSocket()); + + private ObjectMapper objectMapper = Jackson.newObjectMapper(); + private YamlConfigurationFactory yamlConfigurationFactory = new YamlConfigurationFactory<>( + DefaultLoggingFactory.class, BaseValidator.newValidator(), objectMapper, "dw-ssl"); + + @Before + public void setUp() throws Exception { + objectMapper.getSubtypeResolver().registerSubtypes(TcpSocketAppenderFactory.class); + } + + private ServerSocket createServerSocket() { + try { + return createSslContextFactory().newSslServerSocket("localhost", 0, 0); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + private SslContextFactory createSslContextFactory() throws Exception { + SslContextFactory sslContextFactory = new SslContextFactory(); + sslContextFactory.setKeyStorePath(resourcePath("stores/tls_server.jks").getAbsolutePath()); + sslContextFactory.setKeyStorePassword("server_pass"); + sslContextFactory.start(); + return sslContextFactory; + } + + private static File resourcePath(String path) throws URISyntaxException { + return new File(Resources.getResource(path).toURI()); + } + + @Test + public void testTlsLogging() throws Exception { + DefaultLoggingFactory loggingFactory = yamlConfigurationFactory.build(new SubstitutingSourceProvider( + new ResourceConfigurationSourceProvider(), new StrSubstitutor(ImmutableMap.of( + "tls.trust_store.path", resourcePath("stores/tls_client.jks").getAbsolutePath(), + "tls.trust_store.pass", "client_pass", + "tls.server_port", tcpServer.getServerSocket().getLocalPort() + ))), "yaml/logging-tls.yml"); + loggingFactory.configure(new MetricRegistry(), "tls-appender-test"); + + Logger logger = LoggerFactory.getLogger("com.example.app"); + for (int i = 0; i < tcpServer.getMessageCount(); i++) { + logger.info("Application log {}", i); + } + + tcpServer.getLatch().await(5, TimeUnit.SECONDS); + assertThat(tcpServer.getLatch().getCount()).isEqualTo(0); + loggingFactory.reset(); + } + + @Test + public void testParseCustomConfiguration() throws Exception { + DefaultLoggingFactory loggingFactory = yamlConfigurationFactory.build( + resourcePath("yaml/logging-tls-custom.yml")); + assertThat(loggingFactory.getAppenders()).hasSize(1); + TlsSocketAppenderFactory appenderFactory = (TlsSocketAppenderFactory) + loggingFactory.getAppenders().get(0); + assertThat(appenderFactory.getHost()).isEqualTo("172.16.11.244"); + assertThat(appenderFactory.getPort()).isEqualTo(17002); + assertThat(appenderFactory.getKeyStorePath()).isEqualTo("/path/to/keystore.p12"); + assertThat(appenderFactory.getKeyStorePassword()).isEqualTo("keystore_pass"); + assertThat(appenderFactory.getKeyStoreType()).isEqualTo("PKCS12"); + assertThat(appenderFactory.getKeyStoreProvider()).isEqualTo("BC"); + assertThat(appenderFactory.getTrustStorePath()).isEqualTo("/path/to/trust_store.jks"); + assertThat(appenderFactory.getTrustStorePassword()).isEqualTo("trust_store_pass"); + assertThat(appenderFactory.getTrustStoreType()).isEqualTo("JKS"); + assertThat(appenderFactory.getTrustStoreProvider()).isEqualTo("SUN"); + assertThat(appenderFactory.getJceProvider()).isEqualTo("Conscrypt"); + assertThat(appenderFactory.isValidateCerts()).isTrue(); + assertThat(appenderFactory.isValidatePeers()).isTrue(); + assertThat(appenderFactory.getSupportedProtocols()).containsExactly("TLSv1.1", "TLSv1.2"); + assertThat(appenderFactory.getExcludedProtocols()).isEmpty(); + assertThat(appenderFactory.getSupportedCipherSuites()).containsExactly("ECDHE-RSA-AES128-GCM-SHA256", + "ECDHE-ECDSA-AES128-GCM-SHA256"); + assertThat(appenderFactory.getExcludedCipherSuites()).isEmpty(); + } +} diff --git a/dropwizard-logging/src/test/resources/stores/tls_client.jks b/dropwizard-logging/src/test/resources/stores/tls_client.jks new file mode 100644 index 0000000000000000000000000000000000000000..2df5e9f53cba878ca3df1c3925b198ff5dcd6146 GIT binary patch literal 958 zcmezO_TO6u1_mY|W(3n*#l<=C#i>PQsYO7cq{K&F{S2%TdZq@J3=GWG22IT622D)y z3z(T0nV48gd9$kxc-c6$+C196^D;7WvoaXu7z!Bhu`!3TunDt==4I#Qm*>GmI50%m zF+{j9M7S|TnBgJ@a^k#(76!&fW`+jFhQ?-5V6K^w36x7`*EBIIAqNK|D+6;ABR_*d z6C)Q>6C)!-+r%87NdCY||3?~c7Dp`q^E8ZapYOLFMNuE6o~68wI2jeuQuIph(aYDj zMRI;kdEai~t`Nw_7N}nmVEan=el4Go=)GrOT`hXc-J^VNbzVNd`>pRyM=wBmDz&UpIIy` zoIfp^%Xzgn^QnZ+0s z5#KDoEO5u>yU(i@e0b4WomDntyYQ2)irv$cPVZ${^CzVKPjJh(pDTru;>xF6nAmAt z41e}3`@-kI&5f5`dy-!JP1ZgCc;2U74&687oeO1OT=dYGXvP$9RE?p6E#4twvBvbO z!a4Q+_qJROa{VPFkh+F-){5ox*1!LAHLCt!)iwKc4PDjlh0j~$>~h&0gD?J@rm~}N zV)muTs=P(#>^vWO=x=4=H9fdb!p5#)X6f&IrwRHT44IMNy$&i}`Z#@R!6nJtlh(|u znH3(9zQuE8!NTB*aIvC2Hcr~BN*)~J^4^|N{jZ60Z88U2&Vj1*zDN2zQ%k1;06w*B A@&Et; literal 0 HcmV?d00001 diff --git a/dropwizard-logging/src/test/resources/stores/tls_server.jks b/dropwizard-logging/src/test/resources/stores/tls_server.jks new file mode 100644 index 0000000000000000000000000000000000000000..0b2e7aae7840869bce622b542d3461c522947132 GIT binary patch literal 2248 zcmchYX*AT29>!<0G@&w6wyc@bNb^H>F_sCXEM*K4kqBYNPL{@uAu*Ia{0C((6=hAA zWQ)Q0+qV#DY$3aJozA`Y{LktAeet|_KCiy#InQ~%dkcFDAP@+$FYs@{y}jM-yq!pX zPNaS0aB~#21p;wE05WV3;NyhLa)M!CG~@^v%mIRtVQf#For*El7It!lSv@m^WItAX zc@~}1YNvU-Q$@$ac;+67g{F>36hn6v&j{KVS-{tFBtUOO{ESPyujWqRaVJDWm9SDL zsNp_KdR{1Dy`LJ*_JW2F!v;Tt$F0LvW!GGb;zC}+6gQapjB<<_Qu`EoqT#N<(Ac+k zqiyx0HSCIVv`>hGisq=>5_N?8ThL02m8Xe7_# z@^WrSGE_)5gQrd{B56XGxMiG?t0Am^nYFc~%mv)F7;_bA0CfH;Hawdl<;B-}pYgto zdNO_~(e35+Us}VfVjDi=?W<_c3Iu#oUj0`Pf7fz~R~nLHvlL8l$JE9in5L!&RmJJG zx@dRkt3f{;XzDP$U5Ds5=H|Z_MO8_}!dtg^^jezQ3Q`d9o)3y6$uZ!FZ28-rfKbz~(m@e(8;51zHvzwpC1Sta2|KPvi7Ek(rD!n4X; zCu7MdE_y@~&ap@=C3+NQb<(ILYF8CuIj45w_=LNsgObWTMG$7Rj`hq_o@v=I^G`Ac zUZ3WeeSs56jKO^;TG>a6(1%wUUj_pQH`mLGo;@^`rrToT$+@XPYIRRyRzz;*Y^%&D z+Z4n`ao{`R2S`nCBdp&D7r z8+vG{W25)yOR|d%63ju)7p-p(H}Z8ZDZz5M2eD-rRAvX~(xXg7vWa4l^u)Sk?Ld$%jiCyA2gG1z1-@-A2g{;%1Fx`W96u-9paEhG*Ne z#{67~vSs)P#Wc<;7I3WhF@WD_k1}#mSxs0$$poHnvw!|Pz@k1HH!0RWxn)rwfto(c zqnUX0^6cA>0m(+Fi-n$3j?z?qT-$cMFH0{x_{R|RroJ#`11^+XJzt{UKh0-*E-15S zJUFpD#MES<;WSE|rz&ekxGR-Ml*F2z>PcmL&&&LEs5Z1n!Y)^hvAAdyir2mCM(Tpl{y zU+z<6Zw*f=r$04lA$qzT(rLwFY8fNB0KmyYq`qilpml}uBt*N`9j{HJXY0OQ_b#Ir zOse_&J9cTi8#EoJz>$c#ZrTe4OLMYUy5#rlE6|cx1dSuqaa-`$sP^hs>_nMHT~rEs zp*tj3Xy{&}oe%bDpThm0OyBWGC6e+XG*wq#T71-+3%&HiwHu|bw`L$d_#*zEFKj8B z3-)tTbt6TOSH{n{vCt7aKUzdIUAoi*Spx%2r#nn~KlP&|rWJ z^#{lhI|dX2hCpDxyl%k&FXsVtq+#R+FBp7~0|dCsAKsr#s0EA@!DZp$=Hcn@@n6FI z2jTjI!2ciz{~*x+5kO3US6&rRR8W=&6y+6_t$$l(g>(P0|L1GSV5xt`vHvZgWbh#n zKnBAhWH1;Mnc!}0b;vBxWKw26myo|ZjpwVoyk1GNUKXCYHAiT*CQwLhvB__9!$|k- z#3h=lfrJ?!ry0h_^gJ6e9>S+^bZlk~ryA>TU~N1U-P2jKfGH>zZ?R~1lKu{U?Hq0I zs7Sxbpk!edR8C0c zp?~>mIU!}ObE|8iiX%T|Q0y3Td~-HaII@QF)VeusOAO0hOwWF+wpY@&;mDPYt`I>) zIZ4ocrQ0KYxSx-0={5@opPKSq=X;#lWhPqAm94JKpl-Rs3iMWTG7t2I+?f_sNK?C= zQP>HAfwMYY^l6Y;>@zUup&7 z%Y%TLWT{W@Kqb4^!gjAx*0&1z9d7uisGif3>9(BNcI#R-E4$x=d+0EC<*{7nRMtv0 zHfGRHk0?6ZZ77qV3^Dz95){B`hb837qy!N&!c4}>d(Cm%{D+-NIMNI9vr3nCd#%Iv zg8Hv^0bGcEWSN7Ea%VVTkO