Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CHANGELOG.next-release.md
Original file line number Diff line number Diff line change
@@ -1 +1 @@

* Add support of `elastic.otel.verify.server.cert` config option to disable server certificate validation - #726
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,26 @@ public class ElasticAutoConfigurationCustomizerProvider

@Override
public void customize(AutoConfigurationCustomizer autoConfiguration) {
// Order is important: configureExporterUserAgentHeaders needs access to the unwrapped exporters
configureExporterUserAgentHeaders(autoConfiguration);
configureBlockableExporters(autoConfiguration);
// Order is important: headers and server certificate bypass need access to the unwrapped
// exporters and must execute first
autoConfiguration.addSpanExporterCustomizer(
(exporter, config) -> {
exporter = ElasticUserAgentHeader.configureIfPossible(exporter);
exporter = ElasticVerifyServerCert.configureIfPossible(exporter, config);
return BlockableSpanExporter.createCustomInstance(exporter);
});
autoConfiguration.addMetricExporterCustomizer(
(exporter, config) -> {
exporter = ElasticUserAgentHeader.configureIfPossible(exporter);
exporter = ElasticVerifyServerCert.configureIfPossible(exporter, config);
return BlockableMetricExporter.createCustomInstance(exporter);
});
autoConfiguration.addLogRecordExporterCustomizer(
(exporter, config) -> {
exporter = ElasticUserAgentHeader.configureIfPossible(exporter);
exporter = ElasticVerifyServerCert.configureIfPossible(exporter, config);
return BlockableLogRecordExporter.createCustomInstance(exporter);
});

autoConfiguration.addPropertiesCustomizer(
ElasticAutoConfigurationCustomizerProvider::propertiesCustomizer);
Expand All @@ -78,29 +95,6 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) {
});
}

private void configureExporterUserAgentHeaders(AutoConfigurationCustomizer autoConfiguration) {
autoConfiguration.addSpanExporterCustomizer(
(spanExporter, configProperties) ->
ElasticUserAgentHeader.configureIfPossible(spanExporter));
autoConfiguration.addMetricExporterCustomizer(
(metricExporter, configProperties) ->
ElasticUserAgentHeader.configureIfPossible(metricExporter));
autoConfiguration.addLogRecordExporterCustomizer(
(logExporter, configProperties) -> ElasticUserAgentHeader.configureIfPossible(logExporter));
}

private static void configureBlockableExporters(AutoConfigurationCustomizer autoConfiguration) {
autoConfiguration.addMetricExporterCustomizer(
(metricexporter, configProperties) ->
BlockableMetricExporter.createCustomInstance(metricexporter));
autoConfiguration.addSpanExporterCustomizer(
(spanExporter, configProperties) ->
BlockableSpanExporter.createCustomInstance(spanExporter));
autoConfiguration.addLogRecordExporterCustomizer(
(logExporter, configProperties) ->
BlockableLogRecordExporter.createCustomInstance(logExporter));
}

static Map<String, String> propertiesCustomizer(ConfigProperties configProperties) {
Map<String, String> config = new HashMap<>();

Expand Down
187 changes: 187 additions & 0 deletions custom/src/main/java/co/elastic/otel/ElasticVerifyServerCert.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package co.elastic.otel;

import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter;
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporter;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.logs.export.LogRecordExporter;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.GeneralSecurityException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.Properties;
import javax.annotation.Nullable;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

public class ElasticVerifyServerCert {

private static final X509TrustManager X_509_TRUST_ALL =
new X509TrustManager() {
@Override
public void checkClientTrusted(X509Certificate[] chain, String authType) {}

@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) {}

@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[0];
}
};

// package protected for testing
static boolean verifyServerCertificate(ConfigProperties config) {
return config.getBoolean("elastic.otel.verify.server.cert", true);
}

public static SpanExporter configureIfPossible(
SpanExporter spanExporter, ConfigProperties config) {
if (verifyServerCertificate(config)) {
return spanExporter;
}
if (spanExporter instanceof OtlpGrpcSpanExporter) {
return ((OtlpGrpcSpanExporter) spanExporter)
.toBuilder().setSslContext(getSslContext(), X_509_TRUST_ALL).build();
} else if (spanExporter instanceof OtlpHttpSpanExporter) {
return ((OtlpHttpSpanExporter) spanExporter)
.toBuilder().setSslContext(getSslContext(), X_509_TRUST_ALL).build();
}
return spanExporter;
}

public static MetricExporter configureIfPossible(
MetricExporter metricExporter, ConfigProperties config) {
if (verifyServerCertificate(config)) {
return metricExporter;
}
if (metricExporter instanceof OtlpGrpcMetricExporter) {
return ((OtlpGrpcMetricExporter) metricExporter)
.toBuilder().setSslContext(getSslContext(), X_509_TRUST_ALL).build();
} else if (metricExporter instanceof OtlpHttpMetricExporter) {
return ((OtlpHttpMetricExporter) metricExporter)
.toBuilder().setSslContext(getSslContext(), X_509_TRUST_ALL).build();
}
return metricExporter;
}

public static LogRecordExporter configureIfPossible(
LogRecordExporter logExporter, ConfigProperties config) {
if (verifyServerCertificate(config)) {
return logExporter;
}
if (logExporter instanceof OtlpGrpcLogRecordExporter) {
return ((OtlpGrpcLogRecordExporter) logExporter)
.toBuilder().setSslContext(getSslContext(), X_509_TRUST_ALL).build();
} else if (logExporter instanceof OtlpHttpLogRecordExporter) {
return ((OtlpHttpLogRecordExporter) logExporter)
.toBuilder().setSslContext(getSslContext(), X_509_TRUST_ALL).build();
}
return logExporter;
}

private static SSLContext getSslContext() {
SSLContext sslContext;
try {
sslContext = SSLContext.getInstance("TLS");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Unable to create SSL/TLS context", e);
}

KeyManager[] keyManagers = null;
try {
keyManagers = getKeyManagers(System.getProperties());
} catch (IOException | GeneralSecurityException e) {
// silently ignored
// trust and key stores won't be available, which means client certificate can't be used
}

try {
sslContext.init(
keyManagers, new TrustManager[] {X_509_TRUST_ALL}, new java.security.SecureRandom());
} catch (KeyManagementException e) {
throw new IllegalStateException("unable to initialize SSL/TLS context", e);
}

return sslContext;
}

@Nullable
static KeyManager[] getKeyManagers(Properties properties)
throws IOException, GeneralSecurityException {
// re-implements parts of sun.security.ssl.SSLContextImpl.DefaultManagersHolder.getKeyManagers
// as there is no simple way to reuse existing implementation

String path = properties.getProperty("javax.net.ssl.keyStore");
String pwd = properties.getProperty("javax.net.ssl.keyStorePassword");
String type = properties.getProperty("javax.net.ssl.keyStoreType");
String provider = properties.getProperty("javax.net.ssl.keyStoreProvider");

KeyStore ks = null;
try {
if (path != null) {
ks = getKeyStore(Paths.get(path), pwd, type, provider);
}
} catch (IOException | GeneralSecurityException e) {
// silently ignore, client certificate won't work
}

KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
kmf.init(ks, pwd != null ? pwd.toCharArray() : null);
return kmf.getKeyManagers();
}

// package private for testing
@Nullable
static KeyStore getKeyStore(
Path keyStore,
@Nullable String keyStorePassword,
@Nullable String keyStoreType,
@Nullable String keyStoreProvider)
throws IOException, GeneralSecurityException {
String type = keyStoreType != null ? keyStoreType : KeyStore.getDefaultType();
if (keyStore == null) {
return null;
}
try (InputStream input = Files.newInputStream(keyStore)) {
KeyStore ks =
keyStoreProvider == null
? KeyStore.getInstance(type)
: KeyStore.getInstance(type, keyStoreProvider);
ks.load(input, keyStorePassword != null ? keyStorePassword.toCharArray() : null);
return ks;
}
}
}

This file was deleted.

Loading
Loading