尝试从 Docker 访问 ES 实例时出现 SSLHandshakeException

Ewo*_*oks 1 ssl ssl-certificate elasticsearch docker spring-boot

我正在尝试使用高级 REST 客户端 6.7.2 访问 6.x ES 实例。\n通过主机名 (https://****.azureedge.net)、用户名和密码向我提供对此 ES 实例的访问权限。

\n\n

当我的 Spring Boot 应用程序从我的开发环境 (IDE) 运行时,它可以毫无问题地从同一个 ES 获取数据,但当我尝试从 Docker 容器(从我的开发机器或云中的 K8s 集群)运行它时,就会抛出 SSLHandshakeException。

\n\n

FROM debian:stretch-slim容器由基础镜像和OpenJDK 11.0.2Spring Boot 必要模块组成。

\n\n

我使用 进行调试取得了一些进展-Djavax.net.debug=all。事实证明,在 docker 镜像中运行时,只发生了通常 SSL 握手的几个第一步:

\n\n
Produced ClientHello handshake message\nWRITE: TLS13 handshake, length = 2352\nRaw write\nRaw read (0000: 15 03 03 00 02 02 28    ......(   )\nREAD: TLSv1.2 alert, length = 2\nReceived alert message (\n  "Alert": {\n    "level"      : "fatal",\n    "description": "handshake_failure"\n  }\n)\n
Run Code Online (Sandbox Code Playgroud)\n\n

接下来是 SSLHandshakeException:

\n\n
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure\nat org.elasticsearch.client.RestClient$SyncResponseListener.get(RestClient.java:938)\nat org.elasticsearch.client.RestClient.performRequest(RestClient.java:227)\nat org.elasticsearch.client.RestHighLevelClient.internalPerformRequest(RestHighLevelClient.java:1764)\nat org.elasticsearch.client.RestHighLevelClient.performRequest(RestHighLevelClient.java:1749)\nat org.elasticsearch.client.RestHighLevelClient.performRequestAndParseEntity(RestHighLevelClient.java:1708)\nat org.elasticsearch.client.SecurityClient.getSslCertificates(SecurityClient.java:508) \n....\nCaused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure\nat java.base/sun.security.ssl.Alert.createSSLException(Unknown Source)\nat java.base/sun.security.ssl.Alert.createSSLException(Unknown Source)\nat java.base/sun.security.ssl.TransportContext.fatal(Unknown Source)\nat java.base/sun.security.ssl.Alert$AlertConsumer.consume(Unknown Source)\nat java.base/sun.security.ssl.TransportContext.dispatch(Unknown Source)\nat java.base/sun.security.ssl.SSLTransport.decode(Unknown Source)\nat java.base/sun.security.ssl.SSLEngineImpl.decode(Unknown Source)\nat java.base/sun.security.ssl.SSLEngineImpl.readRecord(Unknown Source)\nat java.base/sun.security.ssl.SSLEngineImpl.unwrap(Unknown Source)\nat java.base/sun.security.ssl.SSLEngineImpl.unwrap(Unknown Source)\nat java.base/javax.net.ssl.SSLEngine.unwrap(Unknown Source)\nat org.apache.http.nio.reactor.ssl.SSLIOSession.doUnwrap(SSLIOSession.java:271)\nat org.apache.http.nio.reactor.ssl.SSLIOSession.doHandshake(SSLIOSession.java:316)\nat org.apache.http.nio.reactor.ssl.SSLIOSession.isAppInputReady(SSLIOSession.java:509)\nat org.apache.http.impl.nio.reactor.AbstractIODispatch.inputReady(AbstractIODispatch.java:120)\nat org.apache.http.impl.nio.reactor.BaseIOReactor.readable(BaseIOReactor.java:162)\nat org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvent(AbstractIOReactor.java:337)\nat org.apache.http.impl.nio.reactor.AbstractIOReactor.processEvents(AbstractIOReactor.java:315)\nat org.apache.http.impl.nio.reactor.AbstractIOReactor.execute(AbstractIOReactor.java:276)\nat org.apache.http.impl.nio.reactor.BaseIOReactor.execute(BaseIOReactor.java:104)\nat org.apache.http.impl.nio.reactor.AbstractMultiworkerIOReactor$Worker.run(AbstractMultiworkerIOReactor.java:591)\n
Run Code Online (Sandbox Code Playgroud)\n\n

当从我的本地环境运行时,握手看起来不间断:

\n\n
Produced ClientHello handshake message\nWRITE: TLS13 handshake, length = 460\nRaw write\nRaw read\nREAD: TLSv1.2 handshake, length = 155\nConsuming ServerHello\nServerHello\nNegotiated protocol version: TLSv1.3\nSession initialized:  Session(1560119025211|TLS_AES_256_GCM_SHA384)\nWRITE: TLS13 change_cipher_spec, length = 1\nRaw write\nRaw read\nREAD: TLSv1.2 change_cipher_spec, length = 1\nConsuming ChangeCipherSpec message\nRaw read\nREAD: TLSv1.2 application_data, length = 27\n...\nRaw read\nREAD: TLSv1.2 application_data, length = 8469\nConsuming server Certificate handshake message\n... // here is the list of 3 certificates with "SHA256withRSA", "SHA256withRSA", "SHA1withRSA" signature algorithms\nFound trusted certificate \xe2\x87\xa2 SHA1withRSA\n...\n
Run Code Online (Sandbox Code Playgroud)\n\n

在本地运行时,我注意到CN=Microsoft IT TLS CA 2, OU=Microsoft IT, O=Microsoft Corporation, L=Redmond, ST=Washington, C=US,以及CN=Baltimore CyberTrust Root, OU=CyberTrust, O=Baltimore, C=IE发行者,也许这很重要,但我想考虑到 ES 主机地址(Azure),这是预期的。

\n\n

最后我想强调的是,我不需要做任何特殊的事情就可以在我的macOS Java 11.0.2开发环境中完成这项工作。

\n\n

我已经尝试过以下操作,但这并没有改变任何内容:

\n\n
    \n
  • 将基础 Docker 镜像从“slim”更改为非 slim 版本
  • \n
  • 使用 OpenJDK 11.0.1 或 11.0.2
  • \n
  • 将来自主机的证书添加到 JVM 在运行时使用的 TrustStore。(我检查了 Docker 容器,确实还有一个证书,但考虑到握手失败发生的时间,我想这是无关紧要的)
  • \n
  • 尝试使用以下命令强制应用程序:“-Dcom.sun.net.ssl.enableECC=false”、“-Djdk.tls.client.protocols=TLSv1.3”、“-Dhttps.protocols=TLSv1.3”,没有\'帮不上忙
  • \n
\n\n

有趣的是:来自 Docker 映像的curl 与 BasicAuth 使用相同的 URL 进行“对话”,没有任何问题(握手完成)并且小查询返回结果。我猜想,curl 和 JVM 在 Docker 内使用不同来源的可信 CA、不同的握手算法等。

\n\n

预先感谢您的任何帮助

\n

Ewo*_*oks 6

TLDR:在应用程序中 为客户端强制执行 TLSv1.2 可以从 Docker 内部完成握手

经过多次尝试/失败尝试后,我成功了。以下事情没有任何区别:

  • 使用非“slim”debian 基础镜像代替“slim”
  • 使用 OpenJDK 11.0.2 而不是 11.0.1
  • 在构建 docker 映像时将主机的证书添加到 JVM TrustedStore,以便在容器启动时可用。
  • 执行com.sun.net.ssl.enableECC=false
  • 强制执行 TLSv1.3https.protocols和/或jdk.tls.client.protocols
  • 强制执行 TLSv1.2https.protocols

修复与主机握手的问题-Djdk.tls.client.protocols=TLSv1.2是通过在 Dockerfile 中使用客户端强制执行 TLSv1.2 ,因此应用程序在容器内使用此标志运行。这允许 SSL 握手完成,因为无论如何它都应该工作。由于某种原因,如果不为客户端强制执行较低版本的协议,有关协议版本的实际协商将无法进行。来自本地与 docker 环境的日志没有显示任何差异,但这对 docker 有所帮助。

帮助我发现的是:

  • 设置javax.net.debug=ssl:handshake或更详细,javax.net.debug=all以便我可以看到握手尝试的详细信息
  • 确认“至少有人”可以通过使用curl发送与应用程序尝试相同的请求来从docker内部建立出站通信,这是有效的,因为curl以某种方式弄清楚了如何与主机进行握手。
  • 纯粹的运气

感谢大家的支持和想法