通过 SSL 使用 Elasticsearch 的 Micrometer

Micrometer with Elasticsearch over SSL

我正在尝试通过 SSL.Micrometer 与 Elasticsearch 一起使用

我使用 1.8.0 版的 Micrometer,7.16.3 版的 Elasticsearch 和 OpenJDK 11.0.2。

因为我知道无法使用内置配置 (link) 我尝试注入自定义 HttpUrlConnectionSender 如下 class SecureHttpSender:

public class SecureHttpSender extends HttpUrlConnectionSender {
    ...

    public SecureHttpSender(ElasticProperties properties, SecureElasticProperties secureElasticProperties) {
                super(properties.getConnectTimeout(), properties.getReadTimeout());
                this.secureElasticProperties = secureElasticProperties;
                this.sslSocketFactory = buildSslSocketFactory();
            }
    
    @Override
    public Response send(Request request) throws IOException {
        HttpURLConnection httpURLConnection = null;
        try {
            httpURLConnection = (HttpURLConnection) request.getUrl().openConnection();
    
            // if the connection is an instance of the HttpsURLConnection class, the ssl configuration will always been applied.
            if (httpURLConnection instanceof HttpsURLConnection) {
                // - hostname verifier
                if (!secureElasticProperties.isVerifyHostname()) {
                    logger.debug("setting the hostname verifier to: {}", NoopHostnameVerifier.INSTANCE);
                    ((HttpsURLConnection) httpURLConnection).setHostnameVerifier(NoopHostnameVerifier.INSTANCE);
                }
    
                // - trust store configuration
                ((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(sslSocketFactory);
            }
    
            return super.send(request);
    
        } finally {
            try {
                if (httpURLConnection != null) {
                    httpURLConnection.disconnect();
                }
            } catch (Exception ignore) {
            }
        }
    }
    
    private SSLSocketFactory buildSslSocketFactory() {
        SSLSocketFactory sslSocketFactory;
        try (InputStream is = getInputStream(secureElasticProperties.getTrustStorePath())) {
            KeyStore truststore = KeyStore.getInstance(secureElasticProperties.getTrustStoreType());
            truststore.load(is, secureElasticProperties.getTrustStorePassword().toCharArray());
            SSLContextBuilder sslBuilder = SSLContexts.custom().loadTrustMaterial(truststore, null);
            final SSLContext sslContext = sslBuilder.build();
            sslSocketFactory = sslContext.getSocketFactory();
        } catch (IOException | CertificateException | KeyStoreException | NoSuchAlgorithmException | KeyManagementException e) {
            String message = String.format("error while loading the security configuration from: %s", secureElasticProperties);
            logger.error(message, e);
            throw new RuntimeException("management.metrics.export.elastic.ssl");
        }
        return sslSocketFactory;
    }
    
    private InputStream getInputStream(String trustStorePathString) throws IOException {
    PathMatchingResourcePatternResolver pathMatchingResourcePatternResolver = new PathMatchingResourcePatternResolver();
    Resource resource = pathMatchingResourcePatternResolver.getResource(trustStorePathString);
    return resource.getInputStream();
    }
}

我注入了 Spring 引导,因此我可以应用所需的配置,但我收到以下错误:

ERROR 10912 --- [trics-publisher] i.m.elastic.ElasticMeterRegistry         : failed to send metrics to elastic
javax.net.ssl.SSLHandshakeException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
...
Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
...
Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
...

服务器证书和客户端信任库有效,因为我已经成功使用了它们。 我也尝试在握手阶段强制使用特定版本的TLS协议:TLSv1.3和TLSv1.2,但仍然出现错误。

有人对如何修复它有任何建议吗?谢谢

检查 super.send 做了什么,它创建了一个新连接而不使用您创建的连接。我不建议使用 self-signed 证书和自定义信任库,但您可以设置默认 HostnameVerifier 使用 HttpsURLConnection.setDefaultHostnameVerifier.

由于这是静态的,它将适用于所有 HttpsURLConnection 个实例,因此您无需向 Micrometer 中注入任何东西。

正确的解决方案是使用非 self-signed 证书或适当的信任库(例如:通过 javax.net.ssl.trustStore)。

我做了一个测试,对我发布的代码进行了简单的更改,然后我解决了它: 我复制了 super.send() 方法的所有代码,添加了额外的代码来设置自定义 SslSocketFactory 并且一切正常!

所以原因是

it creates a new connection without using the one you created

正如 Jonatan 所说...这是我的一个小错误。 :)