为什么 Spring Boot 应用程序和 Consul 之间的 SSL 连接会在几分钟后失败?
Why is the SSL connection between a Spring Boot app and Consul failing after a few minutes?
我正在使用新版本的 Ubuntu、Consul 和 Spring Boot 升级环境。乍一看,一切似乎都运行良好。该应用程序连接到 Consul,请求其配置并启动。然而,几分钟后,出现问题并且此消息大约每 2 秒重复一次:
com.ecwid.consul.transport.TransportException: javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
at com.ecwid.consul.transport.AbstractHttpTransport.executeRequest(AbstractHttpTransport.java:77)
at com.ecwid.consul.transport.AbstractHttpTransport.makeGetRequest(AbstractHttpTransport.java:34)
at com.ecwid.consul.v1.ConsulRawClient.makeGetRequest(ConsulRawClient.java:128)
at com.ecwid.consul.v1.catalog.CatalogConsulClient.getCatalogServices(CatalogConsulClient.java:120)
at com.ecwid.consul.v1.ConsulClient.getCatalogServices(ConsulClient.java:372)
at org.springframework.cloud.consul.discovery.ConsulCatalogWatch.catalogServicesWatch(ConsulCatalogWatch.java:129)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
at java.base/sun.security.ssl.SSLSocketImpl.handleEOF(SSLSocketImpl.java:1313)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1152)
at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1055)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:395)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:394)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:353)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:134)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
我追踪到消息每 2 秒出现一次以进行应用程序健康检查。一旦错误第一次出现,它会在后续的每次健康检查中继续出现。通过关闭运行状况检查并重新启动来确认这一点。当达到领事数据上的 TTL 时,这导致错误只发生一次。
我对问题的理解到此为止。我试图将其追溯到几件事,但其中 none 导致了解决方案:
- 检查证书 - 使用的证书由 Vault 生成,由中间证书签名,由自签名根证书签名。然后将它们与密钥一起放入 pkcs12 包中并提供给应用程序。这适用于所有 TLS 连接,也适用于 CLI 工具和
curl
。这似乎是一个死胡同。
- 网络连接 - 由于正在重置连接,我试图查看是否是由于防火墙或安全组问题。但是,相关端口 (8501) 对 TCP 和 UDP 流量都是开放的,所有使用
nc
的手动测试都显示可以访问这些端口。
- IPv6 错误 - 在某处,我发现 post 说这可能是由于 IPv6 的错误。我尝试在机器上关闭 IPv6,重新启动一切并重试。运气不好,还是一样的错误。
- Consul 版本 - 我在我们的旧环境中尝试 运行 应用程序,其中 Consul 1.2.3 是 运行 并且那里的错误没有出现。我仍在尝试找出是否有特定的 Consul 版本开始出现此问题,但尚未找到。
- TLS 错误 - 在 Consul 1.2.3 和 1.7.2 之间,Consul 的 TLS 支持以及底层的 Go TLS 实现发生了一些变化。这是在使用 Consul 1.4.0 进行测试时发现的,它提供了一个略有不同的 TLS 错误。互联网上的一些建议是 Go 和 OpenJDK 之间存在冲突的实现。我尝试强制 Java-应用程序使用 TLS 1.2,但再次失败。
- 握手调试 - 根据评论中的提示,我使用
-Djavax.net.debug=ssl:handshake
找出握手期间发生的情况。在最初的几分钟内,产生的额外输出显示在我看来是正常的握手。一旦出现问题,握手的输出会在 "Produced Client Hello message" 之后立即停止并出现 "Remote host terminated the handshake"。我无法对这个连接的另一端做同样的事情。 Consul 是一个 Golang 应用程序。如果有人知道如何为 Golan 应用程序获取相同的调试信息,请指教。
我希望有人知道如何找到这个问题的原因,或者更好的是,找到解决方案。
经过更多的挖掘和尝试其他版本的东西。我发现使用 GraalVM 会产生不同但更具描述性的错误。当尝试连接到领事应用程序时,它立即终止并显示以下消息:
Caused by: javax.net.ssl.SSLHandshakeException: extension (5) should not be presented in certificate_request
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:307)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:263)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:254)
at java.base/sun.security.ssl.SSLExtensions.<init>(SSLExtensions.java:90)
at java.base/sun.security.ssl.CertificateRequest$T13CertificateRequestMessage.<init>(CertificateRequest.java:818)
at java.base/sun.security.ssl.CertificateRequest$T13CertificateRequestConsumer.consume(CertificateRequest.java:922)
at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:421)
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:177)
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:164)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1151)
at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1062)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:402)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:394)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:353)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:134)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:71)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:220)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:164)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:139)
at com.ecwid.consul.transport.AbstractHttpTransport.executeRequest(AbstractHttpTransport.java:61)
这导致我在 Golang GitHub 页面上遇到了一个问题:https://github.com/golang/go/issues/35722. It details similar issues from various people, but constantly with slightly different details. In that thread, there is mention of a discrepancy between TLS 1.3 implementations between Go and Java. The OpenJDK-maintainers also chip in and refer to this issue: https://bugs.openjdk.java.net/browse/JDK-8236039。
它已被修复并关闭,但在我的任何常规二进制发行版中尚不可用。我将尝试检查该版本是否真正解决了问题。但是,强制 Java 仅使用 TLS1.2 是一种解决方法。您可以通过将 -Djdk.tls.client.protocols=TLSv1.2
添加到启动参数来执行此操作。
我正在使用新版本的 Ubuntu、Consul 和 Spring Boot 升级环境。乍一看,一切似乎都运行良好。该应用程序连接到 Consul,请求其配置并启动。然而,几分钟后,出现问题并且此消息大约每 2 秒重复一次:
com.ecwid.consul.transport.TransportException: javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
at com.ecwid.consul.transport.AbstractHttpTransport.executeRequest(AbstractHttpTransport.java:77)
at com.ecwid.consul.transport.AbstractHttpTransport.makeGetRequest(AbstractHttpTransport.java:34)
at com.ecwid.consul.v1.ConsulRawClient.makeGetRequest(ConsulRawClient.java:128)
at com.ecwid.consul.v1.catalog.CatalogConsulClient.getCatalogServices(CatalogConsulClient.java:120)
at com.ecwid.consul.v1.ConsulClient.getCatalogServices(ConsulClient.java:372)
at org.springframework.cloud.consul.discovery.ConsulCatalogWatch.catalogServicesWatch(ConsulCatalogWatch.java:129)
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305)
at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at java.base/java.lang.Thread.run(Thread.java:834)
Caused by: javax.net.ssl.SSLHandshakeException: Remote host terminated the handshake
at java.base/sun.security.ssl.SSLSocketImpl.handleEOF(SSLSocketImpl.java:1313)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1152)
at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1055)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:395)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:394)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:353)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:134)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
我追踪到消息每 2 秒出现一次以进行应用程序健康检查。一旦错误第一次出现,它会在后续的每次健康检查中继续出现。通过关闭运行状况检查并重新启动来确认这一点。当达到领事数据上的 TTL 时,这导致错误只发生一次。
我对问题的理解到此为止。我试图将其追溯到几件事,但其中 none 导致了解决方案:
- 检查证书 - 使用的证书由 Vault 生成,由中间证书签名,由自签名根证书签名。然后将它们与密钥一起放入 pkcs12 包中并提供给应用程序。这适用于所有 TLS 连接,也适用于 CLI 工具和
curl
。这似乎是一个死胡同。 - 网络连接 - 由于正在重置连接,我试图查看是否是由于防火墙或安全组问题。但是,相关端口 (8501) 对 TCP 和 UDP 流量都是开放的,所有使用
nc
的手动测试都显示可以访问这些端口。 - IPv6 错误 - 在某处,我发现 post 说这可能是由于 IPv6 的错误。我尝试在机器上关闭 IPv6,重新启动一切并重试。运气不好,还是一样的错误。
- Consul 版本 - 我在我们的旧环境中尝试 运行 应用程序,其中 Consul 1.2.3 是 运行 并且那里的错误没有出现。我仍在尝试找出是否有特定的 Consul 版本开始出现此问题,但尚未找到。
- TLS 错误 - 在 Consul 1.2.3 和 1.7.2 之间,Consul 的 TLS 支持以及底层的 Go TLS 实现发生了一些变化。这是在使用 Consul 1.4.0 进行测试时发现的,它提供了一个略有不同的 TLS 错误。互联网上的一些建议是 Go 和 OpenJDK 之间存在冲突的实现。我尝试强制 Java-应用程序使用 TLS 1.2,但再次失败。
- 握手调试 - 根据评论中的提示,我使用
-Djavax.net.debug=ssl:handshake
找出握手期间发生的情况。在最初的几分钟内,产生的额外输出显示在我看来是正常的握手。一旦出现问题,握手的输出会在 "Produced Client Hello message" 之后立即停止并出现 "Remote host terminated the handshake"。我无法对这个连接的另一端做同样的事情。 Consul 是一个 Golang 应用程序。如果有人知道如何为 Golan 应用程序获取相同的调试信息,请指教。
我希望有人知道如何找到这个问题的原因,或者更好的是,找到解决方案。
经过更多的挖掘和尝试其他版本的东西。我发现使用 GraalVM 会产生不同但更具描述性的错误。当尝试连接到领事应用程序时,它立即终止并显示以下消息:
Caused by: javax.net.ssl.SSLHandshakeException: extension (5) should not be presented in certificate_request
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:307)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:263)
at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:254)
at java.base/sun.security.ssl.SSLExtensions.<init>(SSLExtensions.java:90)
at java.base/sun.security.ssl.CertificateRequest$T13CertificateRequestMessage.<init>(CertificateRequest.java:818)
at java.base/sun.security.ssl.CertificateRequest$T13CertificateRequestConsumer.consume(CertificateRequest.java:922)
at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:392)
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:443)
at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:421)
at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:177)
at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:164)
at java.base/sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1151)
at java.base/sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1062)
at java.base/sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:402)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.createLayeredSocket(SSLConnectionSocketFactory.java:394)
at org.apache.http.conn.ssl.SSLConnectionSocketFactory.connectSocket(SSLConnectionSocketFactory.java:353)
at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:134)
at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:353)
at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:380)
at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236)
at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:184)
at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:88)
at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110)
at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:184)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:71)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:220)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:164)
at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:139)
at com.ecwid.consul.transport.AbstractHttpTransport.executeRequest(AbstractHttpTransport.java:61)
这导致我在 Golang GitHub 页面上遇到了一个问题:https://github.com/golang/go/issues/35722. It details similar issues from various people, but constantly with slightly different details. In that thread, there is mention of a discrepancy between TLS 1.3 implementations between Go and Java. The OpenJDK-maintainers also chip in and refer to this issue: https://bugs.openjdk.java.net/browse/JDK-8236039。
它已被修复并关闭,但在我的任何常规二进制发行版中尚不可用。我将尝试检查该版本是否真正解决了问题。但是,强制 Java 仅使用 TLS1.2 是一种解决方法。您可以通过将 -Djdk.tls.client.protocols=TLSv1.2
添加到启动参数来执行此操作。