From 834df21bfa4ec238e62ecd8788358db7ac04983a Mon Sep 17 00:00:00 2001 From: Bjørn Christian Seime Date: Fri, 19 Feb 2021 11:19:05 +0100 Subject: List complete exception chain for ssl handshake failure in connection log --- .../container/logging/ConnectionLogEntry.java | 55 +++++++++++++--------- .../container/logging/JsonConnectionLogWriter.java | 21 +++++---- .../http/server/jetty/JettyConnectionLogger.java | 27 ++++++----- .../logging/JsonConnectionLogWriterTest.java | 12 ++++- .../jdisc/http/server/jetty/HttpServerTest.java | 29 +++++++----- 5 files changed, 89 insertions(+), 55 deletions(-) (limited to 'jdisc_http_service') diff --git a/jdisc_http_service/src/main/java/com/yahoo/container/logging/ConnectionLogEntry.java b/jdisc_http_service/src/main/java/com/yahoo/container/logging/ConnectionLogEntry.java index a6b800a9279..6afe3b74329 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/container/logging/ConnectionLogEntry.java +++ b/jdisc_http_service/src/main/java/com/yahoo/container/logging/ConnectionLogEntry.java @@ -3,6 +3,7 @@ package com.yahoo.container.logging; import java.time.Instant; +import java.util.List; import java.util.Optional; import java.util.UUID; @@ -31,9 +32,7 @@ public class ConnectionLogEntry { private final Instant sslPeerNotBefore; private final Instant sslPeerNotAfter; private final String sslSniServerName; - private final String sslHandshakeFailureException; - private final String sslHandshakeFailureMessage; - private final String sslHandshakeFailureType; + private final SslHandshakeFailure sslHandshakeFailure; private ConnectionLogEntry(Builder builder) { @@ -57,9 +56,7 @@ public class ConnectionLogEntry { this.sslPeerNotBefore = builder.sslPeerNotBefore; this.sslPeerNotAfter = builder.sslPeerNotAfter; this.sslSniServerName = builder.sslSniServerName; - this.sslHandshakeFailureException = builder.sslHandshakeFailureException; - this.sslHandshakeFailureMessage = builder.sslHandshakeFailureMessage; - this.sslHandshakeFailureType = builder.sslHandshakeFailureType; + this.sslHandshakeFailure = builder.sslHandshakeFailure; } public static Builder builder(UUID id, Instant timestamp) { @@ -86,9 +83,33 @@ public class ConnectionLogEntry { public Optional sslPeerNotBefore() { return Optional.ofNullable(sslPeerNotBefore); } public Optional sslPeerNotAfter() { return Optional.ofNullable(sslPeerNotAfter); } public Optional sslSniServerName() { return Optional.ofNullable(sslSniServerName); } - public Optional sslHandshakeFailureException() { return Optional.ofNullable(sslHandshakeFailureException); } - public Optional sslHandshakeFailureMessage() { return Optional.ofNullable(sslHandshakeFailureMessage); } - public Optional sslHandshakeFailureType() { return Optional.ofNullable(sslHandshakeFailureType); } + public Optional sslHandshakeFailure() { return Optional.ofNullable(sslHandshakeFailure); } + + public static class SslHandshakeFailure { + private final String type; + private final List exceptionChain; + + public SslHandshakeFailure(String type, List exceptionChain) { + this.type = type; + this.exceptionChain = List.copyOf(exceptionChain); + } + + public String type() { return type; } + public List exceptionChain() { return exceptionChain; } + + public static class ExceptionEntry { + private final String name; + private final String message; + + public ExceptionEntry(String name, String message) { + this.name = name; + this.message = message; + } + + public String name() { return name; } + public String message() { return message; } + } + } public static class Builder { private final UUID id; @@ -111,9 +132,7 @@ public class ConnectionLogEntry { private Instant sslPeerNotBefore; private Instant sslPeerNotAfter; private String sslSniServerName; - private String sslHandshakeFailureException; - private String sslHandshakeFailureMessage; - private String sslHandshakeFailureType; + private SslHandshakeFailure sslHandshakeFailure; Builder(UUID id, Instant timestamp) { @@ -194,16 +213,8 @@ public class ConnectionLogEntry { this.sslSniServerName = sslSniServerName; return this; } - public Builder withSslHandshakeFailureException(String exception) { - this.sslHandshakeFailureException = exception; - return this; - } - public Builder withSslHandshakeFailureMessage(String message) { - this.sslHandshakeFailureMessage = message; - return this; - } - public Builder withSslHandshakeFailureType(String type) { - this.sslHandshakeFailureType = type; + public Builder withSslHandshakeFailure(SslHandshakeFailure sslHandshakeFailure) { + this.sslHandshakeFailure = sslHandshakeFailure; return this; } diff --git a/jdisc_http_service/src/main/java/com/yahoo/container/logging/JsonConnectionLogWriter.java b/jdisc_http_service/src/main/java/com/yahoo/container/logging/JsonConnectionLogWriter.java index 394f87c07cc..158d2ec4ea6 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/container/logging/JsonConnectionLogWriter.java +++ b/jdisc_http_service/src/main/java/com/yahoo/container/logging/JsonConnectionLogWriter.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonEncoding; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.ObjectMapper; +import com.yahoo.container.logging.ConnectionLogEntry.SslHandshakeFailure.ExceptionEntry; import java.io.IOException; import java.io.OutputStream; @@ -46,13 +47,11 @@ class JsonConnectionLogWriter implements LogWriter { Instant sslPeerNotBefore = unwrap(record.sslPeerNotBefore()); Instant sslPeerNotAfter = unwrap(record.sslPeerNotAfter()); String sslSniServerName = unwrap(record.sslSniServerName()); - String sslHandshakeFailureException = unwrap(record.sslHandshakeFailureException()); - String sslHandshakeFailureMessage = unwrap(record.sslHandshakeFailureMessage()); - String sslHandshakeFailureType = unwrap(record.sslHandshakeFailureType()); + ConnectionLogEntry.SslHandshakeFailure sslHandshakeFailure = unwrap(record.sslHandshakeFailure()); if (isAnyValuePresent( sslProtocol, sslSessionId, sslCipherSuite, sslPeerSubject, sslPeerNotBefore, sslPeerNotAfter, - sslSniServerName, sslHandshakeFailureException, sslHandshakeFailureMessage, sslHandshakeFailureType)) { + sslSniServerName, sslHandshakeFailure)) { generator.writeObjectFieldStart("ssl"); writeOptionalString(generator, "protocol", sslProtocol); @@ -63,11 +62,17 @@ class JsonConnectionLogWriter implements LogWriter { writeOptionalTimestamp(generator, "peerNotAfter", sslPeerNotAfter); writeOptionalString(generator, "sniServerName", sslSniServerName); - if (isAnyValuePresent(sslHandshakeFailureException, sslHandshakeFailureMessage, sslHandshakeFailureType)) { + if (sslHandshakeFailure != null) { generator.writeObjectFieldStart("handshake-failure"); - writeOptionalString(generator, "exception", sslHandshakeFailureException); - writeOptionalString(generator, "message", sslHandshakeFailureMessage); - writeOptionalString(generator, "type", sslHandshakeFailureType); + generator.writeArrayFieldStart("exception"); + for (ExceptionEntry entry : sslHandshakeFailure.exceptionChain()) { + generator.writeStartObject(); + generator.writeStringField("cause", entry.name()); + generator.writeStringField("message", entry.message()); + generator.writeEndObject(); + } + generator.writeEndArray(); + generator.writeStringField("type", sslHandshakeFailure.type()); generator.writeEndObject(); } diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyConnectionLogger.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyConnectionLogger.java index cb3a5d046d8..34a91a3bbb4 100644 --- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyConnectionLogger.java +++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyConnectionLogger.java @@ -3,6 +3,7 @@ package com.yahoo.jdisc.http.server.jetty; import com.yahoo.container.logging.ConnectionLog; import com.yahoo.container.logging.ConnectionLogEntry; +import com.yahoo.container.logging.ConnectionLogEntry.SslHandshakeFailure.ExceptionEntry; import com.yahoo.io.HexDump; import com.yahoo.jdisc.http.ServerConfig; import org.eclipse.jetty.io.Connection; @@ -27,6 +28,7 @@ import javax.net.ssl.StandardConstants; import java.net.InetSocketAddress; import java.security.cert.X509Certificate; import java.time.Instant; +import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects; @@ -222,9 +224,7 @@ class JettyConnectionLogger extends AbstractLifeCycle implements Connection.List private Date sslPeerNotBefore; private Date sslPeerNotAfter; private List sslSniServerNames; - private String sslHandshakeFailureException; - private String sslHandshakeFailureMessage; - private String sslHandshakeFailureType; + private SSLHandshakeException sslHandshakeException; private ConnectionInfo(UUID uuid, long createdAt, InetSocketAddress localAddress, InetSocketAddress peerAddress) { this.uuid = uuid; @@ -284,11 +284,7 @@ class JettyConnectionLogger extends AbstractLifeCycle implements Connection.List } synchronized ConnectionInfo setSslHandshakeFailure(SSLHandshakeException exception) { - this.sslHandshakeFailureException = exception.getClass().getName(); - this.sslHandshakeFailureMessage = exception.getMessage(); - this.sslHandshakeFailureType = SslHandshakeFailure.fromSslHandshakeException(exception) - .map(SslHandshakeFailure::failureType) - .orElse("UNKNOWN"); + this.sslHandshakeException = exception; return this; } @@ -338,10 +334,17 @@ class JettyConnectionLogger extends AbstractLifeCycle implements Connection.List .withSslPeerNotAfter(sslPeerNotAfter.toInstant()) .withSslPeerNotBefore(sslPeerNotBefore.toInstant()); } - if (sslHandshakeFailureException != null && sslHandshakeFailureMessage != null && sslHandshakeFailureType != null) { - builder.withSslHandshakeFailureException(sslHandshakeFailureException) - .withSslHandshakeFailureMessage(sslHandshakeFailureMessage) - .withSslHandshakeFailureType(sslHandshakeFailureType); + if (sslHandshakeException != null) { + List exceptionChain = new ArrayList<>(); + Throwable cause = sslHandshakeException; + while (cause != null) { + exceptionChain.add(new ExceptionEntry(cause.getClass().getName(), cause.getMessage())); + cause = cause.getCause(); + } + String type = SslHandshakeFailure.fromSslHandshakeException(sslHandshakeException) + .map(SslHandshakeFailure::failureType) + .orElse("UNKNOWN"); + builder.withSslHandshakeFailure(new ConnectionLogEntry.SslHandshakeFailure(type, exceptionChain)); } return builder.build(); } diff --git a/jdisc_http_service/src/test/java/com/yahoo/container/logging/JsonConnectionLogWriterTest.java b/jdisc_http_service/src/test/java/com/yahoo/container/logging/JsonConnectionLogWriterTest.java index b8978fe489c..8944ae9d288 100644 --- a/jdisc_http_service/src/test/java/com/yahoo/container/logging/JsonConnectionLogWriterTest.java +++ b/jdisc_http_service/src/test/java/com/yahoo/container/logging/JsonConnectionLogWriterTest.java @@ -1,6 +1,7 @@ package com.yahoo.container.logging;// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. import com.yahoo.test.json.JsonTestHelper; +import com.yahoo.vespa.jdk8compat.List; import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; @@ -20,12 +21,19 @@ class JsonConnectionLogWriterTest { var instant = Instant.parse("2021-01-13T12:12:12Z"); ConnectionLogEntry entry = ConnectionLogEntry.builder(id, instant) .withPeerPort(1234) + .withSslHandshakeFailure(new ConnectionLogEntry.SslHandshakeFailure("UNKNOWN", + List.of( + new ConnectionLogEntry.SslHandshakeFailure.ExceptionEntry("javax.net.ssl.SSLHandshakeException", "message"), + new ConnectionLogEntry.SslHandshakeFailure.ExceptionEntry("java.io.IOException", "cause message")))) .build(); String expectedJson = "{" + "\"id\":\""+id.toString()+"\"," + "\"timestamp\":\"2021-01-13T12:12:12Z\"," + - "\"peerPort\":1234" + - "}"; + "\"peerPort\":1234," + + "\"ssl\":{\"handshake-failure\":{\"exception\":[" + + "{\"cause\":\"javax.net.ssl.SSLHandshakeException\",\"message\":\"message\"}," + + "{\"cause\":\"java.io.IOException\",\"message\":\"cause message\"}" + + "],\"type\":\"UNKNOWN\"}}}"; JsonConnectionLogWriter writer = new JsonConnectionLogWriter(); ByteArrayOutputStream out = new ByteArrayOutputStream(); diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java index 0a4990ff101..c6ed4d42c9b 100644 --- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java +++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java @@ -5,6 +5,7 @@ import com.google.inject.AbstractModule; import com.google.inject.Module; import com.yahoo.container.logging.ConnectionLog; import com.yahoo.container.logging.ConnectionLogEntry; +import com.yahoo.container.logging.ConnectionLogEntry.SslHandshakeFailure.ExceptionEntry; import com.yahoo.container.logging.RequestLog; import com.yahoo.container.logging.RequestLogEntry; import com.yahoo.jdisc.References; @@ -647,9 +648,8 @@ public class HttpServerTest { .add(MetricDefinitions.SSL_HANDSHAKE_FAILURE_MISSING_CLIENT_CERT, 1L, MetricConsumerMock.STATIC_CONTEXT); assertTrue(driver.close()); Assertions.assertThat(connectionLog.logEntries()).hasSize(1); - ConnectionLogEntry logEntry = connectionLog.logEntries().get(0); - Assertions.assertThat(logEntry.sslHandshakeFailureException()).hasValue(SSLHandshakeException.class.getName()); - Assertions.assertThat(logEntry.sslHandshakeFailureType()).hasValue(SslHandshakeFailure.MISSING_CLIENT_CERT.failureType()); + assertSslHandshakeFailurePresent( + connectionLog.logEntries().get(0), SSLHandshakeException.class, SslHandshakeFailure.MISSING_CLIENT_CERT.failureType()); } @Test @@ -672,9 +672,8 @@ public class HttpServerTest { .add(MetricDefinitions.SSL_HANDSHAKE_FAILURE_INCOMPATIBLE_PROTOCOLS, 1L, MetricConsumerMock.STATIC_CONTEXT); assertTrue(driver.close()); Assertions.assertThat(connectionLog.logEntries()).hasSize(1); - ConnectionLogEntry logEntry = connectionLog.logEntries().get(0); - Assertions.assertThat(logEntry.sslHandshakeFailureException()).hasValue(SSLHandshakeException.class.getName()); - Assertions.assertThat(logEntry.sslHandshakeFailureType()).hasValue(SslHandshakeFailure.INCOMPATIBLE_PROTOCOLS.failureType()); + assertSslHandshakeFailurePresent( + connectionLog.logEntries().get(0), SSLHandshakeException.class, SslHandshakeFailure.INCOMPATIBLE_PROTOCOLS.failureType()); } @Test @@ -697,9 +696,8 @@ public class HttpServerTest { .add(MetricDefinitions.SSL_HANDSHAKE_FAILURE_INCOMPATIBLE_CIPHERS, 1L, MetricConsumerMock.STATIC_CONTEXT); assertTrue(driver.close()); Assertions.assertThat(connectionLog.logEntries()).hasSize(1); - ConnectionLogEntry logEntry = connectionLog.logEntries().get(0); - Assertions.assertThat(logEntry.sslHandshakeFailureException()).hasValue(SSLHandshakeException.class.getName()); - Assertions.assertThat(logEntry.sslHandshakeFailureType()).hasValue(SslHandshakeFailure.INCOMPATIBLE_CIPHERS.failureType()); + assertSslHandshakeFailurePresent( + connectionLog.logEntries().get(0), SSLHandshakeException.class, SslHandshakeFailure.INCOMPATIBLE_CIPHERS.failureType()); } @Test @@ -727,8 +725,8 @@ public class HttpServerTest { assertTrue(driver.close()); Assertions.assertThat(connectionLog.logEntries()).hasSize(1); ConnectionLogEntry logEntry = connectionLog.logEntries().get(0); - Assertions.assertThat(logEntry.sslHandshakeFailureException()).hasValue(SSLHandshakeException.class.getName()); - Assertions.assertThat(logEntry.sslHandshakeFailureType()).hasValue(SslHandshakeFailure.INVALID_CLIENT_CERT.failureType()); + assertSslHandshakeFailurePresent( + connectionLog.logEntries().get(0), SSLHandshakeException.class, SslHandshakeFailure.INVALID_CLIENT_CERT.failureType()); } @Test @@ -912,6 +910,15 @@ public class HttpServerTest { } } + private static void assertSslHandshakeFailurePresent( + ConnectionLogEntry entry, Class expectedException, String expectedType) { + Assertions.assertThat(entry.sslHandshakeFailure()).isPresent(); + ConnectionLogEntry.SslHandshakeFailure failure = entry.sslHandshakeFailure().get(); + assertEquals(expectedType, failure.type()); + ExceptionEntry exceptionEntry = failure.exceptionChain().get(0); + assertEquals(expectedException.getName(), exceptionEntry.name()); + } + private static TestDriver createSslWithProxyProtocolTestDriver( Path certificateFile, Path privateKeyFile, RequestLog requestLog, ConnectionLog connectionLog, boolean mixedMode) { -- cgit v1.2.3