From edafbeef13379878fcc6d490759467030ecd9a35 Mon Sep 17 00:00:00 2001 From: Jack Berg <34418638+jack-berg@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:04:03 -0500 Subject: [PATCH] Add setEnabledProtocols option to OTLP exporter builders --- .../opentelemetry-exporter-otlp.txt | 19 +++++- .../opentelemetry-sdk-common.txt | 9 ++- .../otlp/trace/OltpExporterBenchmark.java | 3 +- .../OtlpHttpLogRecordExporterBuilder.java | 17 ++++++ .../OtlpHttpMetricExporterBuilder.java | 17 ++++++ .../trace/OtlpHttpSpanExporterBuilder.java | 17 ++++++ .../otlp/internal/GrpcExporterBuilder.java | 13 +++- .../otlp/internal/HttpExporterBuilder.java | 13 +++- .../internal/ImmutableGrpcSenderConfig.java | 10 +++- .../internal/ImmutableHttpSenderConfig.java | 10 +++- .../OtlpGrpcLogRecordExporterBuilder.java | 17 ++++++ .../OtlpGrpcMetricExporterBuilder.java | 17 ++++++ .../trace/OtlpGrpcSpanExporterBuilder.java | 17 ++++++ .../OtlpGrpcProfilesExporterBuilder.java | 17 ++++++ .../AbstractGrpcTelemetryExporterTest.java | 59 ++++++++++++++++++- .../AbstractHttpTelemetryExporterTest.java | 54 ++++++++++++++++- .../GrpcLogRecordExporterBuilderWrapper.java | 8 +++ .../GrpcMetricExporterBuilderWrapper.java | 7 +++ .../GrpcProfilesExporterBuilderWrapper.java | 7 +++ .../GrpcSpanExporterBuilderWrapper.java | 7 +++ .../HttpLogRecordExporterBuilderWrapper.java | 8 +++ .../HttpMetricExporterBuilderWrapper.java | 7 +++ .../HttpSpanExporterBuilderWrapper.java | 7 +++ ...anagedChannelTelemetryExporterBuilder.java | 6 ++ .../internal/TelemetryExporterBuilder.java | 3 + .../sender/jdk/internal/JdkHttpSender.java | 14 ++++- .../jdk/internal/JdkHttpSenderProvider.java | 3 +- .../jdk/internal/JdkHttpSenderTest.java | 6 +- .../okhttp/internal/OkHttpGrpcSender.java | 13 +++- .../internal/OkHttpGrpcSenderProvider.java | 3 +- .../okhttp/internal/OkHttpHttpSender.java | 19 +++++- .../internal/OkHttpHttpSenderProvider.java | 3 +- .../okhttp/internal/OkHttpGrpcSenderTest.java | 15 +++-- .../internal/OkHttpGrpcSuppressionTest.java | 3 +- .../okhttp/internal/OkHttpHttpSenderTest.java | 6 +- .../internal/OkHttpHttpSuppressionTest.java | 3 +- .../sdk/common/export/GrpcSenderConfig.java | 15 +++++ .../sdk/common/export/HttpSenderConfig.java | 15 +++++ 38 files changed, 453 insertions(+), 34 deletions(-) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-otlp.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-otlp.txt index 9c08edf8a45..1e9dd3dc43a 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-otlp.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-otlp.txt @@ -1,2 +1,19 @@ Comparing source compatibility of opentelemetry-exporter-otlp-1.65.0-SNAPSHOT.jar against opentelemetry-exporter-otlp-1.64.0.jar -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporterBuilder setEnabledProtocols(java.util.List) +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder setEnabledProtocols(java.util.List) +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder setEnabledProtocols(java.util.List) +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.otlp.logs.OtlpGrpcLogRecordExporterBuilder setEnabledProtocols(java.util.List) +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder setEnabledProtocols(java.util.List) +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder setEnabledProtocols(java.util.List) diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt index 8a99a2cd80e..dccc37e4070 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-sdk-common.txt @@ -1,2 +1,9 @@ Comparing source compatibility of opentelemetry-sdk-common-1.65.0-SNAPSHOT.jar against opentelemetry-sdk-common-1.64.0.jar -No changes. \ No newline at end of file +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.common.export.GrpcSenderConfig (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) java.util.List getEnabledProtocols() + +++ NEW ANNOTATION: javax.annotation.Nullable +*** MODIFIED INTERFACE: PUBLIC ABSTRACT io.opentelemetry.sdk.common.export.HttpSenderConfig (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) java.util.List getEnabledProtocols() + +++ NEW ANNOTATION: javax.annotation.Nullable diff --git a/exporters/otlp/all/src/jmh/java/io/opentelemetry/exporter/otlp/trace/OltpExporterBenchmark.java b/exporters/otlp/all/src/jmh/java/io/opentelemetry/exporter/otlp/trace/OltpExporterBenchmark.java index d73bb2989c2..44e5f486ca4 100644 --- a/exporters/otlp/all/src/jmh/java/io/opentelemetry/exporter/otlp/trace/OltpExporterBenchmark.java +++ b/exporters/otlp/all/src/jmh/java/io/opentelemetry/exporter/otlp/trace/OltpExporterBenchmark.java @@ -111,7 +111,8 @@ public void setUp() { null, null, null, - Long.MAX_VALUE), + Long.MAX_VALUE, + null), InternalTelemetryVersion.LATEST, ComponentId.generateLazy(StandardComponentId.ExporterType.OTLP_GRPC_SPAN_EXPORTER), MeterProvider::noop, diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/logs/OtlpHttpLogRecordExporterBuilder.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/logs/OtlpHttpLogRecordExporterBuilder.java index 9918de27b85..5076b684e50 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/logs/OtlpHttpLogRecordExporterBuilder.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/logs/OtlpHttpLogRecordExporterBuilder.java @@ -20,6 +20,7 @@ import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.common.internal.StandardComponentId; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -274,6 +275,22 @@ public OtlpHttpLogRecordExporterBuilder setExecutorService(ExecutorService execu return this; } + /** + * Sets the TLS protocol versions to enable when connecting to an HTTPS endpoint. Pass {@code + * null} to defer to the sender implementation's default. + * + *

Protocol names follow the JSSE convention: {@code "TLSv1.2"}, {@code "TLSv1.3"}, etc. + * + *

Note: enabling legacy protocol versions (TLSv1, TLSv1.1) also requires removing them from + * the JVM's {@code jdk.tls.disabledAlgorithms} security property. + */ + public OtlpHttpLogRecordExporterBuilder setEnabledProtocols(List enabledProtocols) { + requireNonNull(enabledProtocols, "enabledProtocols"); + checkArgument(!enabledProtocols.isEmpty(), "enabledProtocols must not be empty"); + delegate.setEnabledProtocols(enabledProtocols); + return this; + } + /** * Constructs a new instance of the exporter based on the builder's values. * diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterBuilder.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterBuilder.java index d408c5543f5..fb321302df3 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterBuilder.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/metrics/OtlpHttpMetricExporterBuilder.java @@ -26,6 +26,7 @@ import io.opentelemetry.sdk.metrics.export.MetricExporter; import java.time.Duration; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -333,6 +334,22 @@ public OtlpHttpMetricExporterBuilder setExecutorService(ExecutorService executor return this; } + /** + * Sets the TLS protocol versions to enable when connecting to an HTTPS endpoint. Pass {@code + * null} to defer to the sender implementation's default. + * + *

Protocol names follow the JSSE convention: {@code "TLSv1.2"}, {@code "TLSv1.3"}, etc. + * + *

Note: enabling legacy protocol versions (TLSv1, TLSv1.1) also requires removing them from + * the JVM's {@code jdk.tls.disabledAlgorithms} security property. + */ + public OtlpHttpMetricExporterBuilder setEnabledProtocols(List enabledProtocols) { + requireNonNull(enabledProtocols, "enabledProtocols"); + checkArgument(!enabledProtocols.isEmpty(), "enabledProtocols must not be empty"); + delegate.setEnabledProtocols(enabledProtocols); + return this; + } + OtlpHttpMetricExporterBuilder exportAsJson() { delegate.exportAsJson(); return this; diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/trace/OtlpHttpSpanExporterBuilder.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/trace/OtlpHttpSpanExporterBuilder.java index 11f3e2d63a9..decf0e6670b 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/trace/OtlpHttpSpanExporterBuilder.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/http/trace/OtlpHttpSpanExporterBuilder.java @@ -20,6 +20,7 @@ import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.common.internal.StandardComponentId; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -275,6 +276,22 @@ public OtlpHttpSpanExporterBuilder setExecutorService(ExecutorService executorSe return this; } + /** + * Sets the TLS protocol versions to enable when connecting to an HTTPS endpoint. Pass {@code + * null} to defer to the sender implementation's default. + * + *

Protocol names follow the JSSE convention: {@code "TLSv1.2"}, {@code "TLSv1.3"}, etc. + * + *

Note: enabling legacy protocol versions (TLSv1, TLSv1.1) also requires removing them from + * the JVM's {@code jdk.tls.disabledAlgorithms} security property. + */ + public OtlpHttpSpanExporterBuilder setEnabledProtocols(List enabledProtocols) { + requireNonNull(enabledProtocols, "enabledProtocols"); + checkArgument(!enabledProtocols.isEmpty(), "enabledProtocols must not be empty"); + delegate.setEnabledProtocols(enabledProtocols); + return this; + } + /** * Constructs a new instance of the exporter based on the builder's values. * diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/GrpcExporterBuilder.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/GrpcExporterBuilder.java index ad5b9cbd0d1..e22882314b5 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/GrpcExporterBuilder.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/GrpcExporterBuilder.java @@ -68,6 +68,7 @@ public class GrpcExporterBuilder { private ComponentLoader componentLoader = ComponentLoader.forClassLoader(GrpcExporterBuilder.class.getClassLoader()); @Nullable private ExecutorService executorService; + @Nullable private List enabledProtocols; // Use Object type since gRPC may not be on the classpath. @Nullable private Object grpcChannel; @@ -170,6 +171,11 @@ public GrpcExporterBuilder setExecutorService(ExecutorService executorService) { return this; } + public GrpcExporterBuilder setEnabledProtocols(@Nullable List enabledProtocols) { + this.enabledProtocols = enabledProtocols; + return this; + } + @SuppressWarnings("BuilderReturnThis") public GrpcExporterBuilder copy() { GrpcExporterBuilder copy = @@ -189,6 +195,7 @@ public GrpcExporterBuilder copy() { copy.internalTelemetryVersion = internalTelemetryVersion; copy.grpcChannel = grpcChannel; copy.componentLoader = componentLoader; + copy.enabledProtocols = enabledProtocols; return copy; } @@ -232,7 +239,8 @@ public GrpcExporter build() { grpcChannel, // 4mb to align with spec guidance - even though we don't do anything with the // response today, we will so better to have future-looking memory profile - 4 * 1024L * 1024L)); + 4 * 1024L * 1024L, + enabledProtocols)); LOGGER.log(Level.FINE, "Using GrpcSender: " + grpcSender.getClass().getName()); return new GrpcExporter( @@ -265,6 +273,9 @@ public String toString(boolean includePrefixAndSuffix) { if (retryPolicy != null) { joiner.add("retryPolicy=" + retryPolicy); } + if (enabledProtocols != null) { + joiner.add("enabledProtocols=" + enabledProtocols); + } if (grpcChannel != null) { joiner.add("grpcChannel=" + grpcChannel); } diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/HttpExporterBuilder.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/HttpExporterBuilder.java index 98a5a2e2b85..fe950e32730 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/HttpExporterBuilder.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/HttpExporterBuilder.java @@ -69,6 +69,7 @@ public final class HttpExporterBuilder { private ComponentLoader componentLoader = ComponentLoader.forClassLoader(HttpExporterBuilder.class.getClassLoader()); @Nullable private ExecutorService executorService; + @Nullable private List enabledProtocols; public HttpExporterBuilder( StandardComponentId.ExporterType exporterType, String defaultEndpoint) { @@ -167,6 +168,11 @@ public HttpExporterBuilder setExecutorService(ExecutorService executorService) { return this; } + public HttpExporterBuilder setEnabledProtocols(@Nullable List enabledProtocols) { + this.enabledProtocols = enabledProtocols; + return this; + } + public HttpExporterBuilder exportAsJson() { this.exportAsJson = true; exporterType = mapToJsonTypeIfPossible(exporterType); @@ -204,6 +210,7 @@ public HttpExporterBuilder copy() { copy.internalTelemetryVersion = internalTelemetryVersion; copy.proxyOptions = proxyOptions; copy.componentLoader = componentLoader; + copy.enabledProtocols = enabledProtocols; return copy; } @@ -247,7 +254,8 @@ public HttpExporter build() { executorService, // 4mb to align with spec guidance - even though we don't do anything with the // response today, we will so better to have future-looking memory profile - 4 * 1024L * 1024L)); + 4 * 1024L * 1024L, + enabledProtocols)); LOGGER.log(Level.FINE, "Using HttpSender: " + httpSender.getClass().getName()); return new HttpExporter( @@ -282,6 +290,9 @@ public String toString(boolean includePrefixAndSuffix) { if (retryPolicy != null) { joiner.add("retryPolicy=" + retryPolicy); } + if (enabledProtocols != null) { + joiner.add("enabledProtocols=" + enabledProtocols); + } joiner.add("componentLoader=" + componentLoader); if (executorService != null) { joiner.add("executorService=" + executorService); diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/ImmutableGrpcSenderConfig.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/ImmutableGrpcSenderConfig.java index 45b8663e656..58607906a82 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/ImmutableGrpcSenderConfig.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/ImmutableGrpcSenderConfig.java @@ -39,7 +39,8 @@ public static ImmutableGrpcSenderConfig create( @Nullable X509TrustManager trustManager, @Nullable ExecutorService executorService, @Nullable Object managedChannel, - long maxResponseBodySize) { + long maxResponseBodySize, + @Nullable List enabledProtocols) { return new AutoValue_ImmutableGrpcSenderConfig( endpoint, fullMethodName, @@ -52,9 +53,14 @@ public static ImmutableGrpcSenderConfig create( trustManager, executorService, managedChannel, - maxResponseBodySize); + maxResponseBodySize, + enabledProtocols); } @Override public abstract long getMaxResponseBodySize(); + + @Override + @Nullable + public abstract List getEnabledProtocols(); } diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/ImmutableHttpSenderConfig.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/ImmutableHttpSenderConfig.java index a6d04ae6aa4..1f2ac4856d4 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/ImmutableHttpSenderConfig.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/internal/ImmutableHttpSenderConfig.java @@ -36,7 +36,8 @@ static HttpSenderConfig create( @Nullable SSLContext sslContext, @Nullable X509TrustManager trustManager, @Nullable ExecutorService executorService, - long maxResponseBodySize) { + long maxResponseBodySize, + @Nullable List enabledProtocols) { return new AutoValue_ImmutableHttpSenderConfig( endpoint, contentType, @@ -49,9 +50,14 @@ static HttpSenderConfig create( sslContext, trustManager, executorService, - maxResponseBodySize); + maxResponseBodySize, + enabledProtocols); } @Override public abstract long getMaxResponseBodySize(); + + @Override + @Nullable + public abstract List getEnabledProtocols(); } diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcLogRecordExporterBuilder.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcLogRecordExporterBuilder.java index e111962d83e..d7650231576 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcLogRecordExporterBuilder.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/logs/OtlpGrpcLogRecordExporterBuilder.java @@ -21,6 +21,7 @@ import io.opentelemetry.sdk.common.internal.StandardComponentId; import java.net.URI; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -299,6 +300,22 @@ public OtlpGrpcLogRecordExporterBuilder setExecutorService(ExecutorService execu return this; } + /** + * Sets the TLS protocol versions to enable when connecting to an HTTPS endpoint. Pass {@code + * null} to defer to the sender implementation's default. + * + *

Protocol names follow the JSSE convention: {@code "TLSv1.2"}, {@code "TLSv1.3"}, etc. + * + *

Note: enabling legacy protocol versions (TLSv1, TLSv1.1) also requires removing them from + * the JVM's {@code jdk.tls.disabledAlgorithms} security property. + */ + public OtlpGrpcLogRecordExporterBuilder setEnabledProtocols(List enabledProtocols) { + requireNonNull(enabledProtocols, "enabledProtocols"); + checkArgument(!enabledProtocols.isEmpty(), "enabledProtocols must not be empty"); + delegate.setEnabledProtocols(enabledProtocols); + return this; + } + /** * Constructs a new instance of the exporter based on the builder's values. * diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterBuilder.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterBuilder.java index 73e0861e6cd..f36ad06e810 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterBuilder.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/metrics/OtlpGrpcMetricExporterBuilder.java @@ -27,6 +27,7 @@ import java.net.URI; import java.time.Duration; import java.util.Collection; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -357,6 +358,22 @@ public OtlpGrpcMetricExporterBuilder setExecutorService(ExecutorService executor return this; } + /** + * Sets the TLS protocol versions to enable when connecting to an HTTPS endpoint. Pass {@code + * null} to defer to the sender implementation's default. + * + *

Protocol names follow the JSSE convention: {@code "TLSv1.2"}, {@code "TLSv1.3"}, etc. + * + *

Note: enabling legacy protocol versions (TLSv1, TLSv1.1) also requires removing them from + * the JVM's {@code jdk.tls.disabledAlgorithms} security property. + */ + public OtlpGrpcMetricExporterBuilder setEnabledProtocols(List enabledProtocols) { + requireNonNull(enabledProtocols, "enabledProtocols"); + checkArgument(!enabledProtocols.isEmpty(), "enabledProtocols must not be empty"); + delegate.setEnabledProtocols(enabledProtocols); + return this; + } + /** * Constructs a new instance of the exporter based on the builder's values. * diff --git a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcSpanExporterBuilder.java b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcSpanExporterBuilder.java index edceb408381..82706d69ca2 100644 --- a/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcSpanExporterBuilder.java +++ b/exporters/otlp/all/src/main/java/io/opentelemetry/exporter/otlp/trace/OtlpGrpcSpanExporterBuilder.java @@ -21,6 +21,7 @@ import io.opentelemetry.sdk.common.internal.StandardComponentId; import java.net.URI; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -297,6 +298,22 @@ public OtlpGrpcSpanExporterBuilder setExecutorService(ExecutorService executorSe return this; } + /** + * Sets the TLS protocol versions to enable when connecting to an HTTPS endpoint. Pass {@code + * null} to defer to the sender implementation's default. + * + *

Protocol names follow the JSSE convention: {@code "TLSv1.2"}, {@code "TLSv1.3"}, etc. + * + *

Note: enabling legacy protocol versions (TLSv1, TLSv1.1) also requires removing them from + * the JVM's {@code jdk.tls.disabledAlgorithms} security property. + */ + public OtlpGrpcSpanExporterBuilder setEnabledProtocols(List enabledProtocols) { + requireNonNull(enabledProtocols, "enabledProtocols"); + checkArgument(!enabledProtocols.isEmpty(), "enabledProtocols must not be empty"); + delegate.setEnabledProtocols(enabledProtocols); + return this; + } + /** * Constructs a new instance of the exporter based on the builder's values. * diff --git a/exporters/otlp/profiles/src/main/java/io/opentelemetry/exporter/otlp/profiles/OtlpGrpcProfilesExporterBuilder.java b/exporters/otlp/profiles/src/main/java/io/opentelemetry/exporter/otlp/profiles/OtlpGrpcProfilesExporterBuilder.java index 7e50aa9d274..f47c8ea0b65 100644 --- a/exporters/otlp/profiles/src/main/java/io/opentelemetry/exporter/otlp/profiles/OtlpGrpcProfilesExporterBuilder.java +++ b/exporters/otlp/profiles/src/main/java/io/opentelemetry/exporter/otlp/profiles/OtlpGrpcProfilesExporterBuilder.java @@ -18,6 +18,7 @@ import io.opentelemetry.sdk.common.internal.StandardComponentId; import java.net.URI; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -225,6 +226,22 @@ public OtlpGrpcProfilesExporterBuilder setExecutorService(ExecutorService execut return this; } + /** + * Sets the TLS protocol versions to enable when connecting to an HTTPS endpoint. Pass {@code + * null} to defer to the sender implementation's default. + * + *

Protocol names follow the JSSE convention: {@code "TLSv1.2"}, {@code "TLSv1.3"}, etc. + * + *

Note: enabling legacy protocol versions (TLSv1, TLSv1.1) also requires removing them from + * the JVM's {@code jdk.tls.disabledAlgorithms} security property. + */ + public OtlpGrpcProfilesExporterBuilder setEnabledProtocols(List enabledProtocols) { + requireNonNull(enabledProtocols, "enabledProtocols"); + checkArgument(!enabledProtocols.isEmpty(), "enabledProtocols must not be empty"); + delegate.setEnabledProtocols(enabledProtocols); + return this; + } + /** * Constructs a new instance of the exporter based on the builder's values. * diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractGrpcTelemetryExporterTest.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractGrpcTelemetryExporterTest.java index 365fa2ca659..157d26a8ada 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractGrpcTelemetryExporterTest.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractGrpcTelemetryExporterTest.java @@ -220,7 +220,13 @@ public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) sb.http(0); sb.https(0); sb.tls(TlsKeyPair.of(certificate.privateKey(), certificate.certificate())); - sb.tlsCustomizer(ssl -> ssl.trustManager(clientCertificate.certificate())); + sb.tlsCustomizer( + ssl -> { + ssl.trustManager(clientCertificate.certificate()); + // Restrict server to TLS 1.2+ so enabledProtocols tests are meaningful + // regardless of SSL backend (JDK TLS or BoringSSL via netty-tcnative). + ssl.protocols("TLSv1.2", "TLSv1.3"); + }); // Uncomment for detailed request / response logs from server // sb.decorator(LoggingService.newDecorator()); } @@ -635,6 +641,42 @@ void tlsViaSslContext() throws Exception { } } + @Test + void enabledProtocols() throws Exception { + try (TelemetryExporter exporter = + exporterBuilder() + .setEndpoint(server.httpsUri().toString()) + .setTrustedCertificates(Files.readAllBytes(certificate.certificateFile().toPath())) + .setEnabledProtocols(Arrays.asList("TLSv1.2", "TLSv1.3")) + .build()) { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + } + } + + @Test + @SuppressLogger(GrpcExporter.class) + void enabledProtocols_restrictedProtocolsFail() throws Exception { + // UpstreamGrpcSender delegates TLS entirely to the ManagedChannel supplied by the user; + // enabledProtocols is intentionally not applied for that sender. + assumeThat(System.getProperty("io.opentelemetry.sdk.common.export.GrpcSenderProvider")) + .as("enabledProtocols is not supported by UpstreamGrpcSenderProvider") + .isNotEqualTo( + "io.opentelemetry.exporter.sender.grpc.managedchannel.internal.UpstreamGrpcSenderProvider"); + + try (TelemetryExporter exporter = + exporterBuilder() + .setEndpoint(server.httpsUri().toString()) + .setTrustedCertificates(Files.readAllBytes(certificate.certificateFile().toPath())) + .setEnabledProtocols(Collections.singletonList("TLSv1.1")) + .build()) { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isFalse(); + } + } + @Test @SuppressLogger(GrpcExporter.class) void tls_untrusted() { @@ -1077,6 +1119,9 @@ void validConfig() { assertThatCode( () -> exporterBuilder().setTrustedCertificates(certificate.certificate().getEncoded())) .doesNotThrowAnyException(); + + assertThatCode(() -> exporterBuilder().setEnabledProtocols(Arrays.asList("TLSv1.2", "TLSv1.3"))) + .doesNotThrowAnyException(); } @Test @@ -1167,6 +1212,13 @@ void invalidConfig() { .isInstanceOf(IllegalArgumentException.class) .hasMessage( "Unsupported compressionMethod. Compression method must be \"none\" or one of: [base64,gzip]"); + + assertThatThrownBy(() -> exporterBuilder().setEnabledProtocols(null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("enabledProtocols"); + assertThatThrownBy(() -> exporterBuilder().setEnabledProtocols(Collections.emptyList())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("enabledProtocols must not be empty"); } @Test @@ -1192,6 +1244,7 @@ void toBuilderEquality() .setInitialBackoff(Duration.ofMillis(50)) .setBackoffMultiplier(1.3) .build()) + .setEnabledProtocols(Arrays.asList("TLSv1.2", "TLSv1.3")) .build()) { Object unwrapped = exporter.unwrap(); Field builderField = unwrapped.getClass().getDeclaredField("builder"); @@ -1264,6 +1317,7 @@ void stringRepresentation() throws IOException, CertificateEncodingException { .setInitialBackoff(Duration.ofMillis(50)) .setBackoffMultiplier(1.3) .build()) + .setEnabledProtocols(Arrays.asList("TLSv1.2", "TLSv1.3")) .build(); ) { assertThat(telemetryExporter.unwrap().toString()) .matches( @@ -1280,7 +1334,8 @@ void stringRepresentation() throws IOException, CertificateEncodingException { + "headers=Headers\\{.*foo=OBFUSCATED.*\\}, " + "retryPolicy=RetryPolicy\\{maxAttempts=2, initialBackoff=PT0\\.05S, maxBackoff=PT3S, backoffMultiplier=1\\.3, retryExceptionPredicate=null\\}" + ".*" // Maybe additional grpcChannel field, signal specific fields - + "\\}"); + + "\\}") + .contains("enabledProtocols=[TLSv1.2, TLSv1.3]"); } } diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java index 5f5b13b1adf..25b78c170c4 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/AbstractHttpTelemetryExporterTest.java @@ -153,7 +153,13 @@ protected void configure(ServerBuilder sb) { sb.http(0); sb.https(0); sb.tls(TlsKeyPair.of(certificate.privateKey(), certificate.certificate())); - sb.tlsCustomizer(ssl -> ssl.trustManager(clientCertificate.certificate())); + sb.tlsCustomizer( + ssl -> { + ssl.trustManager(clientCertificate.certificate()); + // Restrict server to TLS 1.2+ so enabledProtocols tests are meaningful + // regardless of SSL backend (JDK TLS or BoringSSL via netty-tcnative). + ssl.protocols("TLSv1.2", "TLSv1.3"); + }); // Uncomment for detailed request / response logs from server // sb.decorator(LoggingService.newDecorator()); } @@ -423,6 +429,37 @@ void tlsViaSslContext() throws Exception { } } + @Test + void enabledProtocols() throws Exception { + try (TelemetryExporter exporter = + exporterBuilder() + .setEndpoint(server.httpsUri() + path) + .setTrustedCertificates(Files.readAllBytes(certificate.certificateFile().toPath())) + .setEnabledProtocols(Arrays.asList("TLSv1.2", "TLSv1.3")) + .build()) { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isTrue(); + } + } + + @Test + @SuppressLogger(HttpExporter.class) + void enabledProtocols_restrictedProtocolsFail() throws Exception { + // Restrict to TLSv1.1 only. The test server requires TLS 1.2+, so the handshake fails. + // Even on permissive JVMs, TLSv1.1 is not offered by Armeria's default TLS configuration. + try (TelemetryExporter exporter = + exporterBuilder() + .setEndpoint(server.httpsUri() + path) + .setTrustedCertificates(Files.readAllBytes(certificate.certificateFile().toPath())) + .setEnabledProtocols(Collections.singletonList("TLSv1.1")) + .build()) { + CompletableResultCode result = + exporter.export(Collections.singletonList(generateFakeTelemetry())); + assertThat(result.join(10, TimeUnit.SECONDS).isSuccess()).isFalse(); + } + } + @Test @SuppressLogger(HttpExporter.class) void tls_untrusted() { @@ -854,6 +891,9 @@ void validConfig() { assertThatCode( () -> exporterBuilder().setTrustedCertificates(certificate.certificate().getEncoded())) .doesNotThrowAnyException(); + + assertThatCode(() -> exporterBuilder().setEnabledProtocols(Arrays.asList("TLSv1.2", "TLSv1.3"))) + .doesNotThrowAnyException(); } private void buildAndShutdown(TelemetryExporterBuilder builder) { @@ -913,6 +953,13 @@ void invalidConfig() { .isInstanceOf(IllegalArgumentException.class) .hasMessage( "Unsupported compressionMethod. Compression method must be \"none\" or one of: [base64,gzip]"); + + assertThatThrownBy(() -> exporterBuilder().setEnabledProtocols(null)) + .isInstanceOf(NullPointerException.class) + .hasMessage("enabledProtocols"); + assertThatThrownBy(() -> exporterBuilder().setEnabledProtocols(Collections.emptyList())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("enabledProtocols must not be empty"); } @Test @@ -939,6 +986,7 @@ void toBuilderEquality() .setInitialBackoff(Duration.ofMillis(50)) .setBackoffMultiplier(1.3) .build()) + .setEnabledProtocols(Arrays.asList("TLSv1.2", "TLSv1.3")) .setComponentLoader(ComponentLoader.forClassLoader(new ClassLoader() {})) .build()) { Object unwrapped = exporter.unwrap(); @@ -1043,6 +1091,7 @@ void stringRepresentation() throws IOException, CertificateEncodingException { .setInitialBackoff(Duration.ofMillis(50)) .setBackoffMultiplier(1.3) .build()) + .setEnabledProtocols(Arrays.asList("TLSv1.2", "TLSv1.3")) .build()) { assertThat(telemetryExporter.unwrap().toString()) .matches( @@ -1060,7 +1109,8 @@ void stringRepresentation() throws IOException, CertificateEncodingException { + "headers=Headers\\{.*foo=OBFUSCATED.*\\}, " + "retryPolicy=RetryPolicy\\{maxAttempts=2, initialBackoff=PT0\\.05S, maxBackoff=PT3S, backoffMultiplier=1\\.3, retryExceptionPredicate=null\\}" + ".*" // Maybe additional signal specific fields - + "\\}"); + + "\\}") + .contains("enabledProtocols=[TLSv1.2, TLSv1.3]"); } } diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcLogRecordExporterBuilderWrapper.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcLogRecordExporterBuilderWrapper.java index 87165e2467d..78659b94a6e 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcLogRecordExporterBuilderWrapper.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcLogRecordExporterBuilderWrapper.java @@ -14,6 +14,7 @@ import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.logs.data.LogRecordData; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -137,6 +138,13 @@ public TelemetryExporterBuilder setExecutorService( return this; } + @Override + public TelemetryExporterBuilder setEnabledProtocols( + List enabledProtocols) { + builder.setEnabledProtocols(enabledProtocols); + return this; + } + @Override public TelemetryExporterBuilder setMeterProvider( Supplier meterProviderSupplier) { diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcMetricExporterBuilderWrapper.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcMetricExporterBuilderWrapper.java index f0bda1c9ac4..da08f7c71c4 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcMetricExporterBuilderWrapper.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcMetricExporterBuilderWrapper.java @@ -14,6 +14,7 @@ import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.metrics.data.MetricData; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -135,6 +136,12 @@ public TelemetryExporterBuilder setExecutorService(ExecutorService e return this; } + @Override + public TelemetryExporterBuilder setEnabledProtocols(List enabledProtocols) { + builder.setEnabledProtocols(enabledProtocols); + return this; + } + @Override public TelemetryExporterBuilder setMeterProvider( Supplier meterProviderSupplier) { diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcProfilesExporterBuilderWrapper.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcProfilesExporterBuilderWrapper.java index ac34a9504e2..fbf0af7cd23 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcProfilesExporterBuilderWrapper.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcProfilesExporterBuilderWrapper.java @@ -14,6 +14,7 @@ import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.profiles.data.ProfileData; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -135,6 +136,12 @@ public TelemetryExporterBuilder setExecutorService(ExecutorService return this; } + @Override + public TelemetryExporterBuilder setEnabledProtocols(List enabledProtocols) { + builder.setEnabledProtocols(enabledProtocols); + return this; + } + @Override public TelemetryExporterBuilder setMeterProvider( Supplier meterProviderSupplier) { diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcSpanExporterBuilderWrapper.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcSpanExporterBuilderWrapper.java index 06eb21c5850..838d7edd97a 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcSpanExporterBuilderWrapper.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/GrpcSpanExporterBuilderWrapper.java @@ -14,6 +14,7 @@ import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.trace.data.SpanData; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -135,6 +136,12 @@ public TelemetryExporterBuilder setExecutorService(ExecutorService exe return this; } + @Override + public TelemetryExporterBuilder setEnabledProtocols(List enabledProtocols) { + builder.setEnabledProtocols(enabledProtocols); + return this; + } + @Override public TelemetryExporterBuilder setMeterProvider( Supplier meterProviderSupplier) { diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpLogRecordExporterBuilderWrapper.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpLogRecordExporterBuilderWrapper.java index 8963b2c21b0..7c34a220445 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpLogRecordExporterBuilderWrapper.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpLogRecordExporterBuilderWrapper.java @@ -13,6 +13,7 @@ import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.logs.data.LogRecordData; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -137,6 +138,13 @@ public TelemetryExporterBuilder setExecutorService( return this; } + @Override + public TelemetryExporterBuilder setEnabledProtocols( + List enabledProtocols) { + builder.setEnabledProtocols(enabledProtocols); + return this; + } + @Override public TelemetryExporterBuilder setMeterProvider( Supplier meterProviderSupplier) { diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpMetricExporterBuilderWrapper.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpMetricExporterBuilderWrapper.java index e9152cfac0c..ebeb522cced 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpMetricExporterBuilderWrapper.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpMetricExporterBuilderWrapper.java @@ -13,6 +13,7 @@ import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.metrics.data.MetricData; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -134,6 +135,12 @@ public TelemetryExporterBuilder setExecutorService(ExecutorService e return this; } + @Override + public TelemetryExporterBuilder setEnabledProtocols(List enabledProtocols) { + builder.setEnabledProtocols(enabledProtocols); + return this; + } + @Override public TelemetryExporterBuilder setMeterProvider( Supplier meterProviderSupplier) { diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpSpanExporterBuilderWrapper.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpSpanExporterBuilderWrapper.java index d3a51130ff6..9aa71808484 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpSpanExporterBuilderWrapper.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/HttpSpanExporterBuilderWrapper.java @@ -13,6 +13,7 @@ import io.opentelemetry.sdk.common.export.RetryPolicy; import io.opentelemetry.sdk.trace.data.SpanData; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -133,6 +134,12 @@ public TelemetryExporterBuilder setExecutorService(ExecutorService exe return this; } + @Override + public TelemetryExporterBuilder setEnabledProtocols(List enabledProtocols) { + builder.setEnabledProtocols(enabledProtocols); + return this; + } + @Override public TelemetryExporterBuilder setMeterProvider( Supplier meterProviderSupplier) { diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/ManagedChannelTelemetryExporterBuilder.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/ManagedChannelTelemetryExporterBuilder.java index 55d20c4bd0d..8c6c9757ee9 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/ManagedChannelTelemetryExporterBuilder.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/ManagedChannelTelemetryExporterBuilder.java @@ -211,6 +211,12 @@ public TelemetryExporterBuilder setExecutorService(ExecutorService executorSe return this; } + @Override + public TelemetryExporterBuilder setEnabledProtocols(List enabledProtocols) { + delegate.setEnabledProtocols(enabledProtocols); + return this; + } + @Override public TelemetryExporterBuilder setMeterProvider( Supplier meterProviderSupplier) { diff --git a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/TelemetryExporterBuilder.java b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/TelemetryExporterBuilder.java index 779cc63905e..39ba199f7d1 100644 --- a/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/TelemetryExporterBuilder.java +++ b/exporters/otlp/testing-internal/src/main/java/io/opentelemetry/exporter/otlp/testing/internal/TelemetryExporterBuilder.java @@ -19,6 +19,7 @@ import io.opentelemetry.sdk.profiles.data.ProfileData; import io.opentelemetry.sdk.trace.data.SpanData; import java.time.Duration; +import java.util.List; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; @@ -79,6 +80,8 @@ static TelemetryExporterBuilder wrap(OtlpGrpcProfilesExporterBuilde TelemetryExporterBuilder setExecutorService(ExecutorService executorService); + TelemetryExporterBuilder setEnabledProtocols(List enabledProtocols); + TelemetryExporterBuilder setMeterProvider(Supplier meterProviderSupplier); TelemetryExporterBuilder setInternalTelemetryVersion(InternalTelemetryVersion schemaVersion); diff --git a/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSender.java b/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSender.java index 6c3610e7345..371e3495a3e 100644 --- a/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSender.java +++ b/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSender.java @@ -45,6 +45,7 @@ import javax.annotation.Nullable; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLParameters; /** * {@link HttpSender} which is backed by JDK {@link HttpClient}. @@ -118,9 +119,10 @@ public final class JdkHttpSender implements HttpSender { @Nullable ProxyOptions proxyOptions, @Nullable SSLContext sslContext, @Nullable ExecutorService executorService, - long maxResponseBodySize) { + long maxResponseBodySize, + @Nullable List enabledProtocols) { this( - configureClient(sslContext, connectTimeout, proxyOptions), + configureClient(sslContext, connectTimeout, proxyOptions, enabledProtocols), endpoint, contentType, compressor, @@ -144,7 +146,8 @@ private static ExecutorService newExecutor() { private static HttpClient configureClient( @Nullable SSLContext sslContext, Duration connectTimeout, - @Nullable ProxyOptions proxyOptions) { + @Nullable ProxyOptions proxyOptions, + @Nullable List enabledProtocols) { HttpClient.Builder builder = HttpClient.newBuilder().connectTimeout(connectTimeout); if (sslContext != null) { builder.sslContext(sslContext); @@ -152,6 +155,11 @@ private static HttpClient configureClient( if (proxyOptions != null) { builder.proxy(proxyOptions.getProxySelector()); } + if (enabledProtocols != null && !enabledProtocols.isEmpty()) { + SSLParameters params = new SSLParameters(); + params.setProtocols(enabledProtocols.toArray(new String[0])); + builder.sslParameters(params); + } return builder.build(); } diff --git a/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSenderProvider.java b/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSenderProvider.java index fcd9d64e0fe..1a6c0948893 100644 --- a/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSenderProvider.java +++ b/exporters/sender/jdk/src/main/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSenderProvider.java @@ -30,6 +30,7 @@ public HttpSender createSender(HttpSenderConfig httpSenderConfig) { httpSenderConfig.getProxyOptions(), httpSenderConfig.getSslContext(), httpSenderConfig.getExecutorService(), - httpSenderConfig.getMaxResponseBodySize()); + httpSenderConfig.getMaxResponseBodySize(), + httpSenderConfig.getEnabledProtocols()); } } diff --git a/exporters/sender/jdk/src/test/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSenderTest.java b/exporters/sender/jdk/src/test/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSenderTest.java index e329266b80d..25ff97836a0 100644 --- a/exporters/sender/jdk/src/test/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSenderTest.java +++ b/exporters/sender/jdk/src/test/java/io/opentelemetry/exporter/sender/jdk/internal/JdkHttpSenderTest.java @@ -191,7 +191,8 @@ void defaultExecutor_isBounded() { null, null, null, - Long.MAX_VALUE); + Long.MAX_VALUE, + null); try { int expectedMax = Math.max(Runtime.getRuntime().availableProcessors(), 5); @@ -223,7 +224,8 @@ void connectTimeout() { null, null, null, - Long.MAX_VALUE); + Long.MAX_VALUE, + null); assertThat(sender) .extracting("client", as(InstanceOfAssertFactories.type(HttpClient.class))) diff --git a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java index d9c1e490051..2cf0d830ddf 100644 --- a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java +++ b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSender.java @@ -61,6 +61,7 @@ import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; +import okhttp3.TlsVersion; import okio.Buffer; import okio.GzipSource; @@ -96,7 +97,8 @@ public OkHttpGrpcSender( @Nullable SSLContext sslContext, @Nullable X509TrustManager trustManager, @Nullable ExecutorService executorService, - long maxResponseBodySize) { + long maxResponseBodySize, + @Nullable List enabledProtocols) { int callTimeoutMillis = (int) Math.min(timeout.toMillis(), Integer.MAX_VALUE); int connectTimeoutMillis = (int) Math.min(connectTimeout.toMillis(), Integer.MAX_VALUE); @@ -128,6 +130,15 @@ public OkHttpGrpcSender( if (sslContext != null && trustManager != null) { clientBuilder.sslSocketFactory(sslContext.getSocketFactory(), trustManager); } + if (enabledProtocols != null && !enabledProtocols.isEmpty()) { + TlsVersion[] versions = + enabledProtocols.stream().map(TlsVersion::forJavaName).toArray(TlsVersion[]::new); + clientBuilder.connectionSpecs( + Collections.singletonList( + new ConnectionSpec.Builder(ConnectionSpec.COMPATIBLE_TLS) + .tlsVersions(versions) + .build())); + } } this.client = clientBuilder.build(); diff --git a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderProvider.java b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderProvider.java index fb4b275444f..a1ead79c0d3 100644 --- a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderProvider.java +++ b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderProvider.java @@ -32,6 +32,7 @@ public GrpcSender createSender(GrpcSenderConfig grpcSenderConfig) { grpcSenderConfig.getSslContext(), grpcSenderConfig.getTrustManager(), grpcSenderConfig.getExecutorService(), - grpcSenderConfig.getMaxResponseBodySize()); + grpcSenderConfig.getMaxResponseBodySize(), + grpcSenderConfig.getEnabledProtocols()); } } diff --git a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSender.java b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSender.java index b5c611d1bcb..5a14f546826 100644 --- a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSender.java +++ b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSender.java @@ -41,6 +41,7 @@ import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; +import okhttp3.TlsVersion; import okio.Buffer; import okio.BufferedSink; import okio.GzipSource; @@ -79,7 +80,8 @@ public OkHttpHttpSender( @Nullable SSLContext sslContext, @Nullable X509TrustManager trustManager, @Nullable ExecutorService executorService, - long maxResponseBodySize) { + long maxResponseBodySize, + @Nullable List enabledProtocols) { int callTimeoutMillis = (int) Math.min(timeout.toMillis(), Integer.MAX_VALUE); int connectTimeoutMillis = (int) Math.min(connectTimeout.toMillis(), Integer.MAX_VALUE); @@ -109,8 +111,19 @@ public OkHttpHttpSender( boolean isPlainHttp = endpoint.getScheme().equals("http"); if (isPlainHttp) { builder.connectionSpecs(Collections.singletonList(ConnectionSpec.CLEARTEXT)); - } else if (sslContext != null && trustManager != null) { - builder.sslSocketFactory(sslContext.getSocketFactory(), trustManager); + } else { + if (sslContext != null && trustManager != null) { + builder.sslSocketFactory(sslContext.getSocketFactory(), trustManager); + } + if (enabledProtocols != null && !enabledProtocols.isEmpty()) { + TlsVersion[] versions = + enabledProtocols.stream().map(TlsVersion::forJavaName).toArray(TlsVersion[]::new); + builder.connectionSpecs( + Collections.singletonList( + new ConnectionSpec.Builder(ConnectionSpec.COMPATIBLE_TLS) + .tlsVersions(versions) + .build())); + } } this.client = builder.build(); diff --git a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSenderProvider.java b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSenderProvider.java index 581cf9bb549..8f782c5edd4 100644 --- a/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSenderProvider.java +++ b/exporters/sender/okhttp/src/main/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSenderProvider.java @@ -31,6 +31,7 @@ public HttpSender createSender(HttpSenderConfig httpSenderConfig) { httpSenderConfig.getSslContext(), httpSenderConfig.getTrustManager(), httpSenderConfig.getExecutorService(), - httpSenderConfig.getMaxResponseBodySize()); + httpSenderConfig.getMaxResponseBodySize(), + httpSenderConfig.getEnabledProtocols()); } } diff --git a/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderTest.java b/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderTest.java index d1172f2209a..b5b99877cb2 100644 --- a/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderTest.java +++ b/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSenderTest.java @@ -79,7 +79,8 @@ void send_rejectedExecution_callsOnError() { null, null, executor, - Long.MAX_VALUE); + Long.MAX_VALUE, + null); AtomicReference responseRef = new AtomicReference<>(); AtomicReference errorRef = new AtomicReference<>(); @@ -123,7 +124,8 @@ void shutdown_CompletableResultCodeShouldWaitForThreads() throws Exception { null, null, null, - Long.MAX_VALUE); + Long.MAX_VALUE, + null); CompletableResultCode sendResult = new CompletableResultCode(); sender.send( @@ -167,7 +169,8 @@ void shutdown_NonManagedExecutor_ReturnsImmediately() { null, null, customExecutor, // Pass custom executor -> managedExecutor = false - Long.MAX_VALUE); + Long.MAX_VALUE, + null); CompletableResultCode shutdownResult = sender.shutdown(); @@ -205,7 +208,8 @@ void shutdown_ExecutorDoesNotTerminateInTime_LogsWarningButSucceeds() throws Exc null, null, null, // null executor = managed - Long.MAX_VALUE); + Long.MAX_VALUE, + null); // Start multiple requests to ensure threads are busy CountDownLatch blockCallbacks = new CountDownLatch(1); @@ -268,7 +272,8 @@ void shutdown_InterruptedWhileWaiting_StillSucceeds() throws Exception { null, null, null, - Long.MAX_VALUE); + Long.MAX_VALUE, + null); // Trigger some activity sender.send(new TestMessageWriter(), response -> {}, error -> {}); diff --git a/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSuppressionTest.java b/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSuppressionTest.java index 3bc73283db7..5dfdc1980bb 100644 --- a/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSuppressionTest.java +++ b/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpGrpcSuppressionTest.java @@ -40,6 +40,7 @@ OkHttpGrpcSender createSender(String endpoint) { null, null, null, - Long.MAX_VALUE); + Long.MAX_VALUE, + null); } } diff --git a/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSenderTest.java b/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSenderTest.java index 0edc216aee6..fcd9bb8690e 100644 --- a/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSenderTest.java +++ b/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSenderTest.java @@ -48,7 +48,8 @@ void send_rejectedExecution_callsOnError() { null, null, executor, - Long.MAX_VALUE); + Long.MAX_VALUE, + null); AtomicReference responseRef = new AtomicReference<>(); AtomicReference errorRef = new AtomicReference<>(); @@ -211,7 +212,8 @@ private static OkHttpHttpSender newSender( null, null, executorService, - Long.MAX_VALUE); + Long.MAX_VALUE, + null); } private static class NoOpRequestBodyWriter implements MessageWriter { diff --git a/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSuppressionTest.java b/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSuppressionTest.java index c64247a9b36..5dbfd913a55 100644 --- a/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSuppressionTest.java +++ b/exporters/sender/okhttp/src/test/java/io/opentelemetry/exporter/sender/okhttp/internal/OkHttpHttpSuppressionTest.java @@ -47,6 +47,7 @@ OkHttpHttpSender createSender(String endpoint) { null, null, null, - Long.MAX_VALUE); + Long.MAX_VALUE, + null); } } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/common/export/GrpcSenderConfig.java b/sdk/common/src/main/java/io/opentelemetry/sdk/common/export/GrpcSenderConfig.java index 2444bf1d25b..e4c22901fb9 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/common/export/GrpcSenderConfig.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/common/export/GrpcSenderConfig.java @@ -100,4 +100,19 @@ public interface GrpcSenderConfig { default long getMaxResponseBodySize() { return 4 * 1024L * 1024L; } + + /** + * The TLS protocol versions to enable when connecting to an HTTPS endpoint, or {@code null} to + * defer to the sender implementation's default. + * + *

Protocol names follow the JSSE convention: {@code "TLSv1"}, {@code "TLSv1.1"}, {@code + * "TLSv1.2"}, {@code "TLSv1.3"}. When set, only the listed versions will be enabled. + * + *

Note: enabling legacy protocol versions (TLSv1, TLSv1.1) also requires removing them from + * the JVM's {@code jdk.tls.disabledAlgorithms} security property. + */ + @Nullable + default List getEnabledProtocols() { + return null; + } } diff --git a/sdk/common/src/main/java/io/opentelemetry/sdk/common/export/HttpSenderConfig.java b/sdk/common/src/main/java/io/opentelemetry/sdk/common/export/HttpSenderConfig.java index be40289dd9c..9527bd22cb3 100644 --- a/sdk/common/src/main/java/io/opentelemetry/sdk/common/export/HttpSenderConfig.java +++ b/sdk/common/src/main/java/io/opentelemetry/sdk/common/export/HttpSenderConfig.java @@ -98,4 +98,19 @@ public interface HttpSenderConfig { default long getMaxResponseBodySize() { return 4 * 1024L * 1024L; } + + /** + * The TLS protocol versions to enable when connecting to an HTTPS endpoint, or {@code null} to + * defer to the sender implementation's default. + * + *

Protocol names follow the JSSE convention: {@code "TLSv1"}, {@code "TLSv1.1"}, {@code + * "TLSv1.2"}, {@code "TLSv1.3"}. When set, only the listed versions will be enabled. + * + *

Note: enabling legacy protocol versions (TLSv1, TLSv1.1) also requires removing them from + * the JVM's {@code jdk.tls.disabledAlgorithms} security property. + */ + @Nullable + default List getEnabledProtocols() { + return null; + } }