From 7430348256ee129c450a3d5344eab09831b7f221 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Fri, 7 Apr 2023 17:57:18 -0500 Subject: [PATCH] Add skaffolding for low level exporter SSL API --- exporters/common/build.gradle.kts | 3 - .../exporter/internal/TlsConfigHelper.java | 202 ++++++----------- .../exporter/internal/TlsUtil.java | 25 --- .../internal/grpc/GrpcExporterBuilder.java | 8 +- .../internal/grpc/ManagedChannelUtil.java | 59 ----- .../okhttp/OkHttpExporterBuilder.java | 24 +- .../internal/TlsConfigHelperTest.java | 210 +++++------------- .../otlp/testing-internal/build.gradle.kts | 3 + ...anagedChannelTelemetryExporterBuilder.java | 74 +++++- .../sampler/JaegerRemoteSamplerBuilder.java | 8 +- 10 files changed, 216 insertions(+), 400 deletions(-) diff --git a/exporters/common/build.gradle.kts b/exporters/common/build.gradle.kts index 0e913aa389c..cab0b2e2527 100644 --- a/exporters/common/build.gradle.kts +++ b/exporters/common/build.gradle.kts @@ -22,9 +22,6 @@ dependencies { // dependency on all of our consumers. compileOnly("com.fasterxml.jackson.core:jackson-core") compileOnly("com.squareup.okhttp3:okhttp") - compileOnly("io.grpc:grpc-netty") - compileOnly("io.grpc:grpc-netty-shaded") - compileOnly("io.grpc:grpc-okhttp") compileOnly("io.grpc:grpc-stub") testImplementation(project(":sdk:common")) diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/TlsConfigHelper.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/TlsConfigHelper.java index ce5555e83d7..a897f46f688 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/TlsConfigHelper.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/TlsConfigHelper.java @@ -5,79 +5,76 @@ package io.opentelemetry.exporter.internal; -import com.google.common.annotations.VisibleForTesting; -import java.util.logging.Logger; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; import javax.annotation.Nullable; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; /** - * Utility class to help with management of TLS related components. This class is ultimately - * responsible for enabling TLS via callbacks passed to the configure[...]() methods. This class is - * only intended for internal OpenTelemetry exporter usage and should not be used by end-users. + * Utility class to help with management of TLS related components. TLS config consists {@link + * #keyManager}, {@link #trustManager}, which combine to form {@link #sslContext}. These components + * can be configured via higher level APIs ({@link #createTrustManager(byte[])} and {@link + * #createKeyManager(byte[], byte[])}) which parse keys in PEM format, or the lower level API {@link + * #setSslContext(SSLContext, X509TrustManager)} in which the components are directly set, but NOT + * both. Attempts to reconfigure components which have already been configured throw {@link + * IllegalStateException}. Consumers access components via any combination of {@link + * #getKeyManager()}, {@link #getTrustManager()}, and {@link #getSslContext()}. * *

This class is internal and is hence not for public use. Its APIs are unstable and can change * at any time. */ public class TlsConfigHelper { - private static final Logger logger = Logger.getLogger(TlsConfigHelper.class.getName()); - - private final TlsUtility tlsUtil; - @Nullable private X509KeyManager keyManager; @Nullable private X509TrustManager trustManager; - @Nullable private SSLSocketFactory sslSocketFactory; + @Nullable private SSLContext sslContext; - public TlsConfigHelper() { - this(new TlsUtility() {}); - } - - @VisibleForTesting - TlsConfigHelper(TlsUtility tlsUtil) { - this.tlsUtil = tlsUtil; - } - - /** Sets the X509TrustManager. */ - public TlsConfigHelper setTrustManager(X509TrustManager trustManager) { - this.trustManager = trustManager; - return this; - } + public TlsConfigHelper() {} /** - * Creates a new X509TrustManager from the given cert content. + * Configure the {@link X509TrustManager} from the given cert content. + * + *

Must not be called multiple times, or if {@link #setSslContext(SSLContext, + * X509TrustManager)} has been previously called. * * @param trustedCertsPem Certificate in PEM format. - * @return this */ - public TlsConfigHelper createTrustManager(byte[] trustedCertsPem) { + public void createTrustManager(byte[] trustedCertsPem) { + if (trustManager != null) { + throw new IllegalStateException("trustManager has been previously configured"); + } + try { - this.trustManager = tlsUtil.trustManager(trustedCertsPem); + this.trustManager = TlsUtil.trustManager(trustedCertsPem); } catch (SSLException e) { throw new IllegalStateException( "Error creating X509TrustManager with provided certs. Are they valid X.509 in PEM format?", e); } - return this; } /** - * Creates a new X509KeyManager from the given private key and certificate, both in PEM format. + * Configure the {@link X509KeyManager} from the given private key and certificate, both in PEM + * format. + * + *

Must not be called multiple times, or if {@link #setSslContext(SSLContext, + * X509TrustManager)} has been previously called. * * @param privateKeyPem Private key content in PEM format. * @param certificatePem Certificate content in PEM format. - * @return this */ - public TlsConfigHelper createKeyManager(byte[] privateKeyPem, byte[] certificatePem) { + public void createKeyManager(byte[] privateKeyPem, byte[] certificatePem) { + if (keyManager != null) { + throw new IllegalStateException("keyManager has been previously configured"); + } + try { - if (keyManager != null) { - logger.warning( - "Previous X509 Key manager is being replaced. This is probably an error and should only be set once."); - } - keyManager = tlsUtil.keyManager(privateKeyPem, certificatePem); - return this; + keyManager = TlsUtil.keyManager(privateKeyPem, certificatePem); } catch (SSLException e) { throw new IllegalStateException( "Error creating X509KeyManager with provided certs. Are they valid X.509 in PEM format?", @@ -86,112 +83,51 @@ public TlsConfigHelper createKeyManager(byte[] privateKeyPem, byte[] certificate } /** - * Assigns the X509KeyManager. + * Configure the {@link SSLContext} and {@link X509TrustManager}. + * + *

Must not be called multiple times, or if {@link #createTrustManager(byte[])} or {@link + * #createKeyManager(byte[], byte[])} has been previously called. * - * @return this + * @param sslContext the SSL context. + * @param trustManager the trust manager. */ - public TlsConfigHelper setKeyManager(X509KeyManager keyManager) { - if (this.keyManager != null) { - logger.warning( - "Previous X509 Key manager is being replaced. This is probably an error and should only be set once."); + public void setSslContext(SSLContext sslContext, X509TrustManager trustManager) { + if (this.sslContext != null || this.trustManager != null) { + throw new IllegalStateException("sslContext or trustManager has been previously configured"); } - this.keyManager = keyManager; - return this; - } - - /** - * Sets the SSLSocketFactory, which is passed into the callback within - * configureWithSocketFactory(). - */ - public TlsConfigHelper setSslSocketFactory(SSLSocketFactory sslSocketFactory) { - this.sslSocketFactory = sslSocketFactory; - return this; - } - - /** - * Functional wrapper type used in configure methods. Exists primarily to declare checked - * SSLException. - */ - public interface SslSocketFactoryConfigurer { - void configure(SSLSocketFactory sslSocketFactory, X509TrustManager trustManager) - throws SSLException; + this.trustManager = trustManager; + this.sslContext = sslContext; } - /** - * Functional wrapper type used in configure methods. Exists primarily to declare checked - * SSLException. - */ - public interface KeyManagerConfigurer { - void configure(X509TrustManager trustManager, @Nullable X509KeyManager keyManager) - throws SSLException; + /** Get the {@link X509KeyManager}. */ + @Nullable + public X509KeyManager getKeyManager() { + return keyManager; } - /** - * Configures TLS by invoking the given callback with the X509TrustManager and X509KeyManager. If - * the trust manager or key manager have not yet been configured, this method does nothing. - */ - public void configureWithKeyManager(KeyManagerConfigurer configurer) { - if (trustManager == null) { - return; - } - try { - configurer.configure(trustManager, keyManager); - } catch (SSLException e) { - wrapException(e); - } + /** Get the {@link X509TrustManager}. */ + @Nullable + public X509TrustManager getTrustManager() { + return trustManager; } - /** - * Configures TLS by invoking the provided consumer with a new SSLSocketFactory and the - * preconfigured X509TrustManager. If the trust manager has not been configured, this method does - * nothing. - */ - public void configureWithSocketFactory(SslSocketFactoryConfigurer configurer) { - if (trustManager == null) { - warnIfOtherComponentsConfigured(); - return; + /** Get the {@link SSLContext}. */ + @Nullable + public SSLContext getSslContext() { + if (sslContext != null) { + return sslContext; } try { - SSLSocketFactory sslSocketFactory = this.sslSocketFactory; - if (sslSocketFactory == null) { - sslSocketFactory = tlsUtil.sslSocketFactory(keyManager, trustManager); - } - configurer.configure(sslSocketFactory, trustManager); - } catch (SSLException e) { - wrapException(e); - } - } - - private static void wrapException(SSLException e) { - throw new IllegalStateException( - "Could not configure TLS connection, are certs in valid X.509 in PEM format?", e); - } - - private void warnIfOtherComponentsConfigured() { - if (sslSocketFactory != null) { - logger.warning("sslSocketFactory has been configured without an X509TrustManager."); - return; - } - if (keyManager != null) { - logger.warning("An X509KeyManager has been configured without an X509TrustManager."); - } - } - - // Exists for testing - interface TlsUtility { - default SSLSocketFactory sslSocketFactory( - @Nullable X509KeyManager keyManager, X509TrustManager trustManager) throws SSLException { - return TlsUtil.sslSocketFactory(keyManager, trustManager); - } - - default X509TrustManager trustManager(byte[] trustedCertificatesPem) throws SSLException { - return TlsUtil.trustManager(trustedCertificatesPem); - } - - default X509KeyManager keyManager(byte[] privateKeyPem, byte[] certificatePem) - throws SSLException { - return TlsUtil.keyManager(privateKeyPem, certificatePem); + SSLContext sslContext; + sslContext = SSLContext.getInstance("TLS"); + sslContext.init( + keyManager == null ? null : new KeyManager[] {keyManager}, + trustManager == null ? null : new TrustManager[] {trustManager}, + null); + return sslContext; + } catch (NoSuchAlgorithmException | KeyManagementException e) { + throw new IllegalArgumentException(e); } } } diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/TlsUtil.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/TlsUtil.java index 2be05aed694..4d177402241 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/TlsUtil.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/TlsUtil.java @@ -12,7 +12,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; -import java.security.KeyManagementException; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; @@ -27,12 +26,9 @@ import java.util.ArrayList; import java.util.Base64; import java.util.List; -import javax.annotation.Nullable; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; -import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509KeyManager; @@ -67,27 +63,6 @@ public final class TlsUtil { private TlsUtil() {} - /** Returns a {@link SSLSocketFactory} configured to use the given key and trust manager. */ - public static SSLSocketFactory sslSocketFactory( - @Nullable KeyManager keyManager, TrustManager trustManager) throws SSLException { - - SSLContext sslContext; - try { - sslContext = SSLContext.getInstance("TLS"); - if (keyManager == null) { - sslContext.init(null, new TrustManager[] {trustManager}, null); - } else { - sslContext.init(new KeyManager[] {keyManager}, new TrustManager[] {trustManager}, null); - } - } catch (NoSuchAlgorithmException | KeyManagementException e) { - throw new SSLException( - "Could not set trusted certificates for TLS connection, are they valid " - + "X.509 in PEM format?", - e); - } - return sslContext.getSocketFactory(); - } - /** * Creates {@link KeyManager} initiated by keystore containing single private key with matching * certificate chain. diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/GrpcExporterBuilder.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/GrpcExporterBuilder.java index cade0f018ca..99fae8b839d 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/GrpcExporterBuilder.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/GrpcExporterBuilder.java @@ -29,6 +29,8 @@ import java.util.function.BiFunction; import java.util.function.Supplier; import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509TrustManager; import okhttp3.Headers; import okhttp3.OkHttpClient; import okhttp3.Protocol; @@ -133,7 +135,11 @@ public GrpcExporter build() { clientBuilder.callTimeout(Duration.ofNanos(timeoutNanos)); - tlsConfigHelper.configureWithSocketFactory(clientBuilder::sslSocketFactory); + SSLContext sslContext = tlsConfigHelper.getSslContext(); + X509TrustManager trustManager = tlsConfigHelper.getTrustManager(); + if (sslContext != null && trustManager != null) { + clientBuilder.sslSocketFactory(sslContext.getSocketFactory(), trustManager); + } String endpoint = this.endpoint.resolve(grpcEndpointPath).toString(); if (endpoint.startsWith("http://")) { diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/ManagedChannelUtil.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/ManagedChannelUtil.java index df2b3141d78..8aa46ba44af 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/ManagedChannelUtil.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/grpc/ManagedChannelUtil.java @@ -5,15 +5,10 @@ package io.opentelemetry.exporter.internal.grpc; -import static java.util.Objects.requireNonNull; import static java.util.stream.Collectors.toList; import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; -import io.grpc.netty.GrpcSslContexts; -import io.grpc.netty.NettyChannelBuilder; -import io.netty.handler.ssl.SslContext; -import io.opentelemetry.exporter.internal.TlsUtil; import io.opentelemetry.exporter.internal.retry.RetryPolicy; import io.opentelemetry.exporter.internal.retry.RetryUtil; import io.opentelemetry.sdk.common.CompletableResultCode; @@ -24,10 +19,6 @@ import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; -import javax.annotation.Nullable; -import javax.net.ssl.SSLException; -import javax.net.ssl.X509KeyManager; -import javax.net.ssl.X509TrustManager; /** * Utilities for working with gRPC channels. @@ -39,56 +30,6 @@ public final class ManagedChannelUtil { private static final Logger logger = Logger.getLogger(ManagedChannelUtil.class.getName()); - /** - * Configure the channel builder to trust the certificates. The {@code byte[]} should contain an - * X.509 certificate collection in PEM format. - * - * @throws SSLException if error occur processing the certificates - */ - public static void setClientKeysAndTrustedCertificatesPem( - ManagedChannelBuilder managedChannelBuilder, - X509TrustManager tmf, - @Nullable X509KeyManager kmf) - throws SSLException { - requireNonNull(managedChannelBuilder, "managedChannelBuilder"); - requireNonNull(tmf, "X509TrustManager"); - - // gRPC does not abstract TLS configuration so we need to check the implementation and act - // accordingly. - String channelBuilderClassName = managedChannelBuilder.getClass().getName(); - switch (channelBuilderClassName) { - case "io.grpc.netty.NettyChannelBuilder": - { - NettyChannelBuilder nettyBuilder = (NettyChannelBuilder) managedChannelBuilder; - SslContext sslContext = - GrpcSslContexts.forClient().keyManager(kmf).trustManager(tmf).build(); - nettyBuilder.sslContext(sslContext); - break; - } - case "io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder": - { - io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder nettyBuilder = - (io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder) managedChannelBuilder; - io.grpc.netty.shaded.io.netty.handler.ssl.SslContext sslContext = - io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts.forClient() - .trustManager(tmf) - .keyManager(kmf) - .build(); - nettyBuilder.sslContext(sslContext); - break; - } - case "io.grpc.okhttp.OkHttpChannelBuilder": - io.grpc.okhttp.OkHttpChannelBuilder okHttpBuilder = - (io.grpc.okhttp.OkHttpChannelBuilder) managedChannelBuilder; - okHttpBuilder.sslSocketFactory(TlsUtil.sslSocketFactory(kmf, tmf)); - break; - default: - throw new SSLException( - "TLS certificate configuration not supported for unrecognized ManagedChannelBuilder " - + channelBuilderClassName); - } - } - /** * Convert the {@link RetryPolicy} into a gRPC service config for the {@code serviceName}. The * resulting map can be passed to {@link ManagedChannelBuilder#defaultServiceConfig(Map)}. diff --git a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/okhttp/OkHttpExporterBuilder.java b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/okhttp/OkHttpExporterBuilder.java index 39bc659d9d9..ab4582722b1 100644 --- a/exporters/common/src/main/java/io/opentelemetry/exporter/internal/okhttp/OkHttpExporterBuilder.java +++ b/exporters/common/src/main/java/io/opentelemetry/exporter/internal/okhttp/OkHttpExporterBuilder.java @@ -18,8 +18,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import javax.annotation.Nullable; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.X509KeyManager; +import javax.net.ssl.SSLContext; import javax.net.ssl.X509TrustManager; import okhttp3.Headers; import okhttp3.OkHttpClient; @@ -95,23 +94,14 @@ public OkHttpExporterBuilder configureTrustManager(byte[] trustedCertificates return this; } - public OkHttpExporterBuilder setTrustManager(X509TrustManager trustManager) { - tlsConfigHelper.setTrustManager(trustManager); - return this; - } - public OkHttpExporterBuilder configureKeyManager(byte[] privateKeyPem, byte[] certificatePem) { tlsConfigHelper.createKeyManager(privateKeyPem, certificatePem); return this; } - public OkHttpExporterBuilder setKeyManager(X509KeyManager keyManager) { - tlsConfigHelper.setKeyManager(keyManager); - return this; - } - - public OkHttpExporterBuilder setSslSocketFactory(SSLSocketFactory sslSocketFactory) { - tlsConfigHelper.setSslSocketFactory(sslSocketFactory); + public OkHttpExporterBuilder setSslContext( + SSLContext sslContext, X509TrustManager trustManager) { + tlsConfigHelper.setSslContext(sslContext, trustManager); return this; } @@ -136,7 +126,11 @@ public OkHttpExporter build() { .dispatcher(OkHttpUtil.newDispatcher()) .callTimeout(Duration.ofNanos(timeoutNanos)); - tlsConfigHelper.configureWithSocketFactory(clientBuilder::sslSocketFactory); + SSLContext sslContext = tlsConfigHelper.getSslContext(); + X509TrustManager trustManager = tlsConfigHelper.getTrustManager(); + if (sslContext != null && trustManager != null) { + clientBuilder.sslSocketFactory(sslContext.getSocketFactory(), trustManager); + } Headers headers = headersBuilder == null ? null : headersBuilder.build(); diff --git a/exporters/common/src/test/java/io/opentelemetry/exporter/internal/TlsConfigHelperTest.java b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/TlsConfigHelperTest.java index d6e871881e5..31af6f6c1c4 100644 --- a/exporters/common/src/test/java/io/opentelemetry/exporter/internal/TlsConfigHelperTest.java +++ b/exporters/common/src/test/java/io/opentelemetry/exporter/internal/TlsConfigHelperTest.java @@ -6,26 +6,16 @@ package io.opentelemetry.exporter.internal; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension; -import io.github.netmikey.logunit.api.LogCapturer; import io.opentelemetry.internal.testing.slf4j.SuppressLogger; -import java.util.concurrent.atomic.AtomicReference; -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSocketFactory; -import javax.net.ssl.X509KeyManager; +import java.security.cert.CertificateEncodingException; +import javax.net.ssl.SSLContext; import javax.net.ssl.X509TrustManager; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; -import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) @@ -34,170 +24,74 @@ class TlsConfigHelperTest { @RegisterExtension static final SelfSignedCertificateExtension serverTls = new SelfSignedCertificateExtension(); - @RegisterExtension - LogCapturer logs = LogCapturer.create().captureForLogger(TlsConfigHelper.class.getName()); - - @Mock TlsConfigHelper.TlsUtility tlsUtil; - - @Test - void createTrustManager_exceptionMapped() throws Exception { - when(tlsUtil.trustManager(any())).thenThrow(new SSLException("boom")); - TlsConfigHelper helper = new TlsConfigHelper(tlsUtil); - assertThrows( - IllegalStateException.class, - () -> { - helper.createTrustManager(serverTls.certificate().getEncoded()); - }); - } + private TlsConfigHelper helper = new TlsConfigHelper(); @Test - void createKeymanager_exceptionMapped() throws Exception { - when(tlsUtil.keyManager(any(), any())).thenThrow(new SSLException("boom")); - TlsConfigHelper helper = new TlsConfigHelper(tlsUtil); - assertThrows( - IllegalStateException.class, - () -> { - helper.createKeyManager( - serverTls.privateKey().getEncoded(), serverTls.certificate().getEncoded()); - }); - } - - @Test - void socketFactory_happyPathWithBytes() throws Exception { - - byte[] cert = serverTls.certificate().getEncoded(); - byte[] key = serverTls.privateKey().getEncoded(); - AtomicReference seenTrustManager = new AtomicReference<>(); - AtomicReference seenSocketFactory = new AtomicReference<>(); - - X509TrustManager trustManager = mock(X509TrustManager.class); - X509KeyManager keyManager = mock(X509KeyManager.class); - SSLSocketFactory sslSocketFactory = mock(SSLSocketFactory.class); - - when(tlsUtil.trustManager(cert)).thenReturn(trustManager); - when(tlsUtil.keyManager(key, cert)).thenReturn(keyManager); - when(tlsUtil.sslSocketFactory(keyManager, trustManager)).thenReturn(sslSocketFactory); - - TlsConfigHelper helper = new TlsConfigHelper(tlsUtil); - - helper.createKeyManager(key, cert); - helper.createTrustManager(cert); - helper.configureWithSocketFactory( - (sf, tm) -> { - seenTrustManager.set(tm); - seenSocketFactory.set(sf); - }); - assertSame(trustManager, seenTrustManager.get()); - assertSame(sslSocketFactory, seenSocketFactory.get()); - } + void createTrustManager() throws CertificateEncodingException { + helper.createTrustManager(serverTls.certificate().getEncoded()); - @Test - void socketFactory_noTrustManager() { - TlsConfigHelper helper = new TlsConfigHelper(tlsUtil); - helper.configureWithSocketFactory( - (sf, tm) -> { - fail("Should not have called due to missing trust manager"); - }); - verifyNoInteractions(tlsUtil); + assertThat(helper.getTrustManager()).isNotNull(); } @Test - void socketFactoryCallbackExceptionHandled() { - X509TrustManager trustManager = mock(X509TrustManager.class); - - TlsConfigHelper helper = new TlsConfigHelper(tlsUtil); - - helper.setTrustManager(trustManager); - assertThrows( - IllegalStateException.class, - () -> { - helper.configureWithSocketFactory( - (sf, tm) -> { - throw new SSLException("bad times"); - }); - }); + void createTrustManager_AlreadyExists_Throws() throws Exception { + helper.createTrustManager(serverTls.certificate().getEncoded()); + assertThatThrownBy(() -> helper.createTrustManager(serverTls.certificate().getEncoded())) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("trustManager has been previously configured"); + + helper = new TlsConfigHelper(); + helper.setSslContext( + SSLContext.getInstance("TLS"), TlsUtil.trustManager(serverTls.certificate().getEncoded())); + assertThatThrownBy(() -> helper.createTrustManager(serverTls.certificate().getEncoded())) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("trustManager has been previously configured"); } @Test - void configureWithKeyManagerHappyPath() { - AtomicReference seenTrustManager = new AtomicReference<>(); - AtomicReference seenKeyManager = new AtomicReference<>(); - - X509TrustManager trustManager = mock(X509TrustManager.class); - X509KeyManager keyManager = mock(X509KeyManager.class); - - TlsConfigHelper helper = new TlsConfigHelper(tlsUtil); - - helper.setTrustManager(trustManager); - helper.setKeyManager(keyManager); - helper.configureWithKeyManager( - (tm, km) -> { - seenTrustManager.set(tm); - seenKeyManager.set(km); - }); - assertThat(seenTrustManager.get()).isSameAs(trustManager); - assertThat(seenKeyManager.get()).isSameAs(keyManager); - } + void createKeyManager() throws CertificateEncodingException { + helper.createKeyManager( + serverTls.privateKey().getEncoded(), serverTls.certificate().getEncoded()); - @Test - void configureWithKeyManagerExceptionHandled() { - X509TrustManager trustManager = mock(X509TrustManager.class); - X509KeyManager keyManager = mock(X509KeyManager.class); - - TlsConfigHelper helper = new TlsConfigHelper(tlsUtil); - - helper.setTrustManager(trustManager); - helper.setKeyManager(keyManager); - assertThrows( - IllegalStateException.class, - () -> - helper.configureWithKeyManager( - (trustManager1, keyManager1) -> { - throw new SSLException("ouchey"); - })); + assertThat(helper.getKeyManager()).isNotNull(); } @Test - void configureWithKeyManagerNoTrustManager() { - TlsConfigHelper helper = new TlsConfigHelper(tlsUtil); - helper.configureWithKeyManager( - (tm, km) -> { - fail(); - }); - verifyNoInteractions(tlsUtil); + void createKeyManager_AlreadyExists_Throws() throws Exception { + helper.createKeyManager( + serverTls.privateKey().getEncoded(), serverTls.certificate().getEncoded()); + assertThatThrownBy( + () -> + helper.createKeyManager( + serverTls.privateKey().getEncoded(), serverTls.certificate().getEncoded())) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("keyManager has been previously configured"); } @Test - void setKeyManagerReplacesAndWarns() { - X509KeyManager keyManager1 = mock(X509KeyManager.class); - X509KeyManager keyManager2 = mock(X509KeyManager.class); - - TlsConfigHelper helper = new TlsConfigHelper(tlsUtil); - - helper.setTrustManager(mock(X509TrustManager.class)); - helper.setKeyManager(keyManager1); - helper.setKeyManager(keyManager2); + void setSslContext() throws Exception { + SSLContext sslContext = SSLContext.getInstance("TLS"); + X509TrustManager trustManager = TlsUtil.trustManager(serverTls.certificate().getEncoded()); + helper.setSslContext(sslContext, trustManager); - helper.configureWithKeyManager((tm, km) -> assertSame(km, keyManager2)); - logs.assertContains("Previous X509 Key manager is being replaced"); + assertThat(helper.getSslContext()).isEqualTo(sslContext); + assertThat(helper.getTrustManager()).isEqualTo(trustManager); } @Test - void createKeyManagerReplacesAndWarns() throws Exception { - X509KeyManager keyManager1 = mock(X509KeyManager.class); - X509KeyManager keyManager2 = mock(X509KeyManager.class); - - byte[] cert = serverTls.certificate().getEncoded(); - byte[] key = serverTls.privateKey().getEncoded(); - - when(tlsUtil.keyManager(key, cert)).thenReturn(keyManager2); - TlsConfigHelper helper = new TlsConfigHelper(tlsUtil); - - helper.setTrustManager(mock(X509TrustManager.class)); - helper.setKeyManager(keyManager1); - helper.createKeyManager(key, cert); - - helper.configureWithKeyManager((tm, km) -> assertSame(km, keyManager2)); - logs.assertContains("Previous X509 Key manager is being replaced"); + void setSslContext_AlreadyExists_Throws() throws Exception { + SSLContext sslContext = SSLContext.getInstance("TLS"); + X509TrustManager trustManager = TlsUtil.trustManager(serverTls.certificate().getEncoded()); + + helper.setSslContext(sslContext, trustManager); + assertThatThrownBy(() -> helper.setSslContext(sslContext, trustManager)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("sslContext or trustManager has been previously configured"); + + helper = new TlsConfigHelper(); + helper.createTrustManager(serverTls.certificate().getEncoded()); + assertThatThrownBy(() -> helper.setSslContext(sslContext, trustManager)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("sslContext or trustManager has been previously configured"); } } diff --git a/exporters/otlp/testing-internal/build.gradle.kts b/exporters/otlp/testing-internal/build.gradle.kts index f420d877790..70433a47f8e 100644 --- a/exporters/otlp/testing-internal/build.gradle.kts +++ b/exporters/otlp/testing-internal/build.gradle.kts @@ -18,6 +18,9 @@ dependencies { // Must be compileOnly so gRPC isn't on the classpath for non-gRPC tests. compileOnly("io.grpc:grpc-stub") + compileOnly("io.grpc:grpc-netty") + compileOnly("io.grpc:grpc-netty-shaded") + compileOnly("io.grpc:grpc-okhttp") implementation(project(":testing-internal")) 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 7ff40964207..bedaa381141 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 @@ -9,6 +9,9 @@ import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; +import io.grpc.netty.GrpcSslContexts; +import io.grpc.netty.NettyChannelBuilder; +import io.netty.handler.ssl.SslContext; import io.opentelemetry.exporter.internal.TlsConfigHelper; import io.opentelemetry.exporter.internal.grpc.ManagedChannelUtil; import io.opentelemetry.exporter.internal.otlp.OtlpUserAgent; @@ -19,6 +22,9 @@ import java.util.Collection; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.X509TrustManager; /** * Wraps a {@link TelemetryExporterBuilder}, delegating methods to upstream gRPC's {@link @@ -124,11 +130,11 @@ public TelemetryExporterBuilder setChannel(ManagedChannel channel) { public TelemetryExporter build() { requireNonNull(channelBuilder, "channel"); - tlsConfigHelper.configureWithKeyManager( - (tm, km) -> { - requireNonNull(channelBuilder, "channel"); - ManagedChannelUtil.setClientKeysAndTrustedCertificatesPem(channelBuilder, tm, km); - }); + try { + setSslContext(channelBuilder, tlsConfigHelper); + } catch (SSLException e) { + throw new IllegalStateException(e); + } ManagedChannel channel = channelBuilder.build(); delegate.setChannel(channel); @@ -151,4 +157,62 @@ public CompletableResultCode shutdown() { } }; } + + /** + * Configure the channel builder to trust the certificates. The {@code byte[]} should contain an + * X.509 certificate collection in PEM format. + * + * @throws SSLException if error occur processing the certificates + */ + private static void setSslContext( + ManagedChannelBuilder managedChannelBuilder, TlsConfigHelper tlsConfigHelper) + throws SSLException { + requireNonNull(managedChannelBuilder, "managedChannelBuilder"); + X509TrustManager trustManager = tlsConfigHelper.getTrustManager(); + if (trustManager == null) { + return; + } + + // gRPC does not abstract TLS configuration so we need to check the implementation and act + // accordingly. + String channelBuilderClassName = managedChannelBuilder.getClass().getName(); + switch (channelBuilderClassName) { + case "io.grpc.netty.NettyChannelBuilder": + { + NettyChannelBuilder nettyBuilder = (NettyChannelBuilder) managedChannelBuilder; + SslContext sslContext = + GrpcSslContexts.forClient() + .keyManager(tlsConfigHelper.getKeyManager()) + .trustManager(trustManager) + .build(); + nettyBuilder.sslContext(sslContext); + break; + } + case "io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder": + { + io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder nettyBuilder = + (io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder) managedChannelBuilder; + io.grpc.netty.shaded.io.netty.handler.ssl.SslContext sslContext = + io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts.forClient() + .trustManager(trustManager) + .keyManager(tlsConfigHelper.getKeyManager()) + .build(); + nettyBuilder.sslContext(sslContext); + break; + } + case "io.grpc.okhttp.OkHttpChannelBuilder": + SSLContext sslContext = tlsConfigHelper.getSslContext(); + if (sslContext == null) { + return; + } + io.grpc.okhttp.OkHttpChannelBuilder okHttpBuilder = + (io.grpc.okhttp.OkHttpChannelBuilder) managedChannelBuilder; + okHttpBuilder.sslSocketFactory(sslContext.getSocketFactory()); + break; + default: + throw new SSLException( + "TLS certificate configuration not supported for unrecognized ManagedChannelBuilder " + + channelBuilderClassName); + } + } } diff --git a/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerBuilder.java b/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerBuilder.java index d1c74895267..34a0de74b13 100644 --- a/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerBuilder.java +++ b/sdk-extensions/jaeger-remote-sampler/src/main/java/io/opentelemetry/sdk/extension/trace/jaeger/sampler/JaegerRemoteSamplerBuilder.java @@ -19,6 +19,8 @@ import java.util.Collections; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509TrustManager; import okhttp3.Headers; import okhttp3.OkHttpClient; import okhttp3.Protocol; @@ -153,7 +155,11 @@ public JaegerRemoteSampler build() { clientBuilder.callTimeout(Duration.ofNanos(TimeUnit.SECONDS.toNanos(DEFAULT_TIMEOUT_SECS))); - tlsConfigHelper.configureWithSocketFactory(clientBuilder::sslSocketFactory); + SSLContext sslContext = tlsConfigHelper.getSslContext(); + X509TrustManager trustManager = tlsConfigHelper.getTrustManager(); + if (sslContext != null && trustManager != null) { + clientBuilder.sslSocketFactory(sslContext.getSocketFactory(), trustManager); + } String endpoint = this.endpoint.resolve(GRPC_ENDPOINT_PATH).toString(); if (endpoint.startsWith("http://")) {