尝试从Docker访问ES实例时发生SSLHandshakeException

时间:2019-06-10 08:38:30

标签: spring-boot docker ssl elasticsearch ssl-certificate

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

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

容器由基本映像:FROM debian:stretch-slimOpenJDK 11.0.2组成,并带有Spring Boot必需的模块。

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

Produced ClientHello handshake message
WRITE: TLS13 handshake, length = 2352
Raw write
Raw read (0000: 15 03 03 00 02 02 28    ......(   )
READ: TLSv1.2 alert, length = 2
Received alert message (
  "Alert": {
    "level"      : "fatal",
    "description": "handshake_failure"
  }
)

后面跟随SSLHandshakeException:

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

从本地环境运行时,握手看起来不会中断:

Produced ClientHello handshake message
WRITE: TLS13 handshake, length = 460
Raw write
Raw read
READ: TLSv1.2 handshake, length = 155
Consuming ServerHello
ServerHello
Negotiated protocol version: TLSv1.3
Session initialized:  Session(1560119025211|TLS_AES_256_GCM_SHA384)
WRITE: TLS13 change_cipher_spec, length = 1
Raw write
Raw read
READ: TLSv1.2 change_cipher_spec, length = 1
Consuming ChangeCipherSpec message
Raw read
READ: TLSv1.2 application_data, length = 27
...
Raw read
READ: TLSv1.2 application_data, length = 8469
Consuming server Certificate handshake message
... // here is the list of 3 certificates with "SHA256withRSA", "SHA256withRSA", "SHA1withRSA" signature algorithms
Found trusted certificate ⇢ SHA1withRSA
...

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

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

>

我已经尝试过跟踪,但是并没有改变任何东西:

  • 将基本Docker映像从“苗条”更改为非苗条版本
  • 使用OpenJDK 11.0.1或11.0.2
  • 已将主机在运行时中使用的证书从主机添加到TrustStore。 (我在Docker容器中检查确实还有一个证书,但是考虑到握手失败发生的时间,我认为这是不相关的)
  • 试图通过以下方式强制执行A​​pp:“-Dcom.sun.net.ssl.enableECC = false”,“-Djdk.tls.client.protocols = TLSv1.3”,“-Dhttps.protocols = TLSv1.3”,没有帮助

有趣:从具有BasicAuth的Docker镜像卷曲,并且URL相同且没有问题(握手完成),并且小查询返回结果。我猜想curl和JVM在Docker内部使用了不同来源的受信任CA,在握手中使用了不同的算法等。

在此先感谢您的帮助

1 个答案:

答案 0 :(得分:0)

TLDR: 在应用程序中为客户端执行TLSv1.2 ,使得从Docker内部完成握手

在多次尝试/失败尝试之后,我使它起作用。以下内容没有任何区别:

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

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

帮我找出来的是:

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

感谢大家的支持和想法