diff options
Diffstat (limited to 'jdisc_http_service/src/main/java/com/yahoo/jdisc')
3 files changed, 98 insertions, 7 deletions
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 01d6fa02d6e..2796ee2d271 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 @@ -42,7 +42,7 @@ public class ConnectorFactory { public ServerConnector createConnector(final Metric metric, final Server server, final ServerSocketChannel ch) { ServerConnector connector = new JDiscServerConnector( - connectorConfig, metric, server, ch, createConnectionFactories().toArray(ConnectionFactory[]::new)); + connectorConfig, metric, server, ch, createConnectionFactories(metric).toArray(ConnectionFactory[]::new)); connector.setPort(connectorConfig.listenPort()); connector.setName(connectorConfig.name()); connector.setAcceptQueueSize(connectorConfig.acceptQueueSize()); @@ -51,14 +51,14 @@ public class ConnectorFactory { return connector; } - private List<ConnectionFactory> createConnectionFactories() { + private List<ConnectionFactory> createConnectionFactories(Metric metric) { HttpConnectionFactory httpConnectionFactory = newHttpConnectionFactory(); if (connectorConfig.healthCheckProxy().enable()) { return List.of(httpConnectionFactory); } else if (connectorConfig.ssl().enabled()) { - return List.of(newSslConnectionFactory(), httpConnectionFactory); + return List.of(newSslConnectionFactory(metric), httpConnectionFactory); } else if (TransportSecurityUtils.isTransportSecurityEnabled()) { - SslConnectionFactory sslConnectionsFactory = newSslConnectionFactory(); + SslConnectionFactory sslConnectionsFactory = newSslConnectionFactory(metric); switch (TransportSecurityUtils.getInsecureMixedMode()) { case TLS_CLIENT_MIXED_SERVER: case PLAINTEXT_CLIENT_MIXED_SERVER: @@ -88,9 +88,11 @@ public class ConnectorFactory { return new HttpConnectionFactory(httpConfig); } - private SslConnectionFactory newSslConnectionFactory() { - SslContextFactory factory = sslContextFactoryProvider.getInstance(connectorConfig.name(), connectorConfig.listenPort()); - return new SslConnectionFactory(factory, HttpVersion.HTTP_1_1.asString()); + private SslConnectionFactory newSslConnectionFactory(Metric metric) { + SslContextFactory ctxFactory = sslContextFactoryProvider.getInstance(connectorConfig.name(), connectorConfig.listenPort()); + SslConnectionFactory connectionFactory = new SslConnectionFactory(ctxFactory, HttpVersion.HTTP_1_1.asString()); + connectionFactory.addBean(new SslHandshakeFailedListener(metric, connectorConfig.name(), connectorConfig.listenPort())); + return connectionFactory; } private OptionalSslConnectionFactory newOptionalSslConnectionFactory(SslConnectionFactory sslConnectionsFactory) { diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java index 140feb75026..5c658d7b3df 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java @@ -114,6 +114,12 @@ public class JettyHttpServer extends AbstractServerProvider { String URI_LENGTH = "jdisc.http.request.uri_length"; String CONTENT_SIZE = "jdisc.http.request.content_size"; + + String SSL_HANDSHAKE_FAILURE_MISSING_CLIENT_CERT = "jdisc.http.ssl.handshake_failure.missing_client_cert"; + String SSL_HANDSHAKE_FAILURE_INVALID_CLIENT_CERT = "jdisc.http.ssl.handshake_failure.invalid_client_cert"; + String SSL_HANDSHAKE_FAILURE_INCOMPATIBLE_PROTOCOLS = "jdisc.http.ssl.handshake_failure.incompatible_protocols"; + String SSL_HANDSHAKE_FAILURE_INCOMPATIBLE_CIPHERS = "jdisc.http.ssl.handshake_failure.incompatible_ciphers"; + String SSL_HANDSHAKE_FAILURE_UNKNOWN = "jdisc.http.ssl.handshake_failure.unknown"; } private final static Logger log = Logger.getLogger(JettyHttpServer.class.getName()); diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SslHandshakeFailedListener.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SslHandshakeFailedListener.java new file mode 100644 index 00000000000..84f4fd118cc --- /dev/null +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SslHandshakeFailedListener.java @@ -0,0 +1,83 @@ +// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.jdisc.http.server.jetty; + +import com.yahoo.jdisc.Metric; +import com.yahoo.jdisc.http.server.jetty.JettyHttpServer.Metrics; +import org.eclipse.jetty.io.ssl.SslHandshakeListener; + +import javax.net.ssl.SSLHandshakeException; +import java.util.Map; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +/** + * A {@link SslHandshakeListener} that reports metrics for SSL handshake failures. + * + * @author bjorncs + */ +class SslHandshakeFailedListener implements SslHandshakeListener { + + private final static Logger log = Logger.getLogger(SslHandshakeFailedListener.class.getName()); + + private final Metric metric; + private final String connectorName; + private final int listenPort; + + SslHandshakeFailedListener(Metric metric, String connectorName, int listenPort) { + this.metric = metric; + this.connectorName = connectorName; + this.listenPort = listenPort; + } + + @Override + public void handshakeFailed(Event event, Throwable throwable) { + log.log(Level.FINE, throwable, () -> "Ssl handshake failed: " + throwable.getMessage()); + String metricName = SslHandshakeFailure.fromSslHandshakeException((SSLHandshakeException) throwable) + .map(SslHandshakeFailure::metricName) + .orElse(Metrics.SSL_HANDSHAKE_FAILURE_UNKNOWN); + metric.add(metricName, 1L, metric.createContext(createDimensions())); + } + + private Map<String, Object> createDimensions() { + return Map.of(Metrics.NAME_DIMENSION, connectorName, Metrics.PORT_DIMENSION, listenPort); + } + + private enum SslHandshakeFailure { + INCOMPATIBLE_PROTOCOLS( + Metrics.SSL_HANDSHAKE_FAILURE_INCOMPATIBLE_PROTOCOLS, + "(Client requested protocol \\S+? is not enabled or supported in server context" + + "|The client supported protocol versions \\[\\S+?\\] are not accepted by server preferences \\[\\S+?\\])"), + INCOMPATIBLE_CIPHERS( + Metrics.SSL_HANDSHAKE_FAILURE_INCOMPATIBLE_CIPHERS, + "no cipher suites in common"), + MISSING_CLIENT_CERT( + Metrics.SSL_HANDSHAKE_FAILURE_MISSING_CLIENT_CERT, + "Empty server certificate chain"), + INVALID_CLIENT_CERT( + Metrics.SSL_HANDSHAKE_FAILURE_INVALID_CLIENT_CERT, + "PKIX path (building|validation) failed: .+"); + + private final String metricName; + private final Predicate<String> messageMatcher; + + SslHandshakeFailure(String metricName, String messagePattern) { + this.metricName = metricName; + this.messageMatcher = Pattern.compile(messagePattern).asMatchPredicate(); + } + + String metricName() { return metricName; } + + static Optional<SslHandshakeFailure> fromSslHandshakeException(SSLHandshakeException exception) { + String message = exception.getMessage(); + for (SslHandshakeFailure failure : values()) { + if (failure.messageMatcher.test(message)) { + return Optional.of(failure); + } + } + return Optional.empty(); + } + } +} |