I'm trying to use Micrometer with Elasticsearch over SSL.
I use Micrometer in version 1.8.0, Elasticsearch in version 7.16.3 and OpenJDK 11.0.2 .
Because I know that it's not possible to use a built-in configuration (link) I tried to inject a custom HttpUrlConnectionSender as in the following 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();
}
}
that I injected with Spring Boot so I can apply the desired configuration, but I got the following error:
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
...
The server certificate and the client truststore are valid because I already used them with success. I also tried to force a specific version of the TLS protocol during the handshake phase: TLSv1.3 and TLSv1.2 but the error still occurs.
Anyone have any suggestions on how to fix it? thanks
CodePudding user response:
Check what super.send
does, it creates a new connection without using the one you created. I'm not recommending using a self-signed cert and a custom truststore but you can set a default HostnameVerifier
using
HttpsURLConnection.setDefaultHostnameVerifier
.
Since this is static, it will work for all HttpsURLConnection
instances so you don't need to inject anything into Micrometer.
The right solution would be either using a non-self-signed cert or a proper truststore (e.g.: via javax.net.ssl.trustStore
).
CodePudding user response:
I did a test with a simple change to the code I had posted and I solved it: I copied all code of the super.send() method, adding the additional code to set the custom SslSocketFactory and all was OK!
so the reason was that
it creates a new connection without using the one you created
as Jonatan said... a my trivial mistake. :)