Add TlsConfigHelper for additional TLS configurability (#5246)

* add TlsConfigHelper for additional TLS configurability and wire up internal builders.

* add javadoc and spotless

* add keys for "validity" testing.

* fix tests

* fix tests

* address code review comments

* fix typo

* allow keymanager to be nullable.

* fix test

* so....much...null......away

* backfill tests

* checkstyle

* test coverage

* address code review comments
This commit is contained in:
jason plumb 2023-03-07 10:54:26 -08:00 committed by GitHub
parent e871805ee2
commit 895075f9e5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 524 additions and 156 deletions

View File

@ -0,0 +1,197 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.exporter.internal;
import com.google.common.annotations.VisibleForTesting;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocketFactory;
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.
*
* <p>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;
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;
}
/**
* Creates a new X509TrustManager from the given cert content.
*
* @param trustedCertsPem Certificate in PEM format.
* @return this
*/
public TlsConfigHelper createTrustManager(byte[] trustedCertsPem) {
try {
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.
*
* @param privateKeyPem Private key content in PEM format.
* @param certificatePem Certificate content in PEM format.
* @return this
*/
public TlsConfigHelper createKeyManager(byte[] privateKeyPem, byte[] certificatePem) {
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;
} catch (SSLException e) {
throw new IllegalStateException(
"Error creating X509KeyManager with provided certs. Are they valid X.509 in PEM format?",
e);
}
}
/**
* Assigns the X509KeyManager.
*
* @return this
*/
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.");
}
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;
}
/**
* 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;
}
/**
* 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);
}
}
/**
* 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;
}
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);
}
}
}

View File

@ -89,7 +89,7 @@ public final class TlsUtil {
}
/**
* Creates {@link KeyManager} initiaded by keystore containing single private key with matching
* Creates {@link KeyManager} initiated by keystore containing single private key with matching
* certificate chain.
*/
public static X509KeyManager keyManager(byte[] privateKeyPem, byte[] certificatePem)

View File

@ -14,7 +14,7 @@ import io.grpc.stub.MetadataUtils;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.exporter.internal.ExporterBuilderUtil;
import io.opentelemetry.exporter.internal.TlsUtil;
import io.opentelemetry.exporter.internal.TlsConfigHelper;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
import io.opentelemetry.exporter.internal.okhttp.OkHttpUtil;
import io.opentelemetry.exporter.internal.retry.RetryInterceptor;
@ -29,9 +29,6 @@ import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import javax.net.ssl.SSLException;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.Headers;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
@ -55,9 +52,7 @@ public class GrpcExporterBuilder<T extends Marshaler> {
private URI endpoint;
private boolean compressionEnabled = false;
private final Map<String, String> headers = new HashMap<>();
@Nullable private byte[] trustedCertificatesPem;
@Nullable private byte[] privateKeyPem;
@Nullable private byte[] certificatePem;
private final TlsConfigHelper tlsConfigHelper = new TlsConfigHelper();
@Nullable private RetryPolicy retryPolicy;
private Supplier<MeterProvider> meterProviderSupplier = GlobalOpenTelemetry::getMeterProvider;
@ -103,14 +98,13 @@ public class GrpcExporterBuilder<T extends Marshaler> {
return this;
}
public GrpcExporterBuilder<T> setTrustedCertificates(byte[] trustedCertificatesPem) {
this.trustedCertificatesPem = trustedCertificatesPem;
public GrpcExporterBuilder<T> configureTrustManager(byte[] trustedCertificatesPem) {
tlsConfigHelper.createTrustManager(trustedCertificatesPem);
return this;
}
public GrpcExporterBuilder<T> setClientTls(byte[] privateKeyPem, byte[] certificatePem) {
this.privateKeyPem = privateKeyPem;
this.certificatePem = certificatePem;
public GrpcExporterBuilder<T> configureKeyManager(byte[] privateKeyPem, byte[] certificatePem) {
tlsConfigHelper.createKeyManager(privateKeyPem, certificatePem);
return this;
}
@ -139,20 +133,7 @@ public class GrpcExporterBuilder<T extends Marshaler> {
clientBuilder.callTimeout(Duration.ofNanos(timeoutNanos));
if (trustedCertificatesPem != null) {
try {
X509TrustManager trustManager = TlsUtil.trustManager(trustedCertificatesPem);
X509KeyManager keyManager = null;
if (privateKeyPem != null && certificatePem != null) {
keyManager = TlsUtil.keyManager(privateKeyPem, certificatePem);
}
clientBuilder.sslSocketFactory(
TlsUtil.sslSocketFactory(keyManager, trustManager), trustManager);
} catch (SSLException e) {
throw new IllegalStateException(
"Could not set trusted certificates, are they valid X.509 in PEM format?", e);
}
}
tlsConfigHelper.configureWithSocketFactory(clientBuilder::sslSocketFactory);
String endpoint = this.endpoint.resolve(grpcEndpointPath).toString();
if (endpoint.startsWith("http://")) {

View File

@ -12,6 +12,7 @@ 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;
@ -46,47 +47,45 @@ public final class ManagedChannelUtil {
*/
public static void setClientKeysAndTrustedCertificatesPem(
ManagedChannelBuilder<?> managedChannelBuilder,
@Nullable byte[] privateKeyPem,
@Nullable byte[] certificatePem,
byte[] trustedCertificatesPem)
X509TrustManager tmf,
@Nullable X509KeyManager kmf)
throws SSLException {
requireNonNull(managedChannelBuilder, "managedChannelBuilder");
requireNonNull(trustedCertificatesPem, "trustedCertificatesPem");
X509TrustManager tmf = TlsUtil.trustManager(trustedCertificatesPem);
X509KeyManager kmf = null;
if (privateKeyPem != null && certificatePem != null) {
kmf = TlsUtil.keyManager(privateKeyPem, certificatePem);
}
requireNonNull(tmf, "X509TrustManager");
// gRPC does not abstract TLS configuration so we need to check the implementation and act
// accordingly.
if (managedChannelBuilder.getClass().getName().equals("io.grpc.netty.NettyChannelBuilder")) {
NettyChannelBuilder nettyBuilder = (NettyChannelBuilder) managedChannelBuilder;
nettyBuilder.sslContext(
GrpcSslContexts.forClient().keyManager(kmf).trustManager(tmf).build());
} else if (managedChannelBuilder
.getClass()
.getName()
.equals("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;
nettyBuilder.sslContext(
io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts.forClient()
.trustManager(tmf)
.keyManager(kmf)
.build());
} else if (managedChannelBuilder
.getClass()
.getName()
.equals("io.grpc.okhttp.OkHttpChannelBuilder")) {
io.grpc.okhttp.OkHttpChannelBuilder okHttpBuilder =
(io.grpc.okhttp.OkHttpChannelBuilder) managedChannelBuilder;
okHttpBuilder.sslSocketFactory(TlsUtil.sslSocketFactory(kmf, tmf));
} else {
throw new SSLException(
"TLS certificate configuration not supported for unrecognized ManagedChannelBuilder "
+ managedChannelBuilder.getClass().getName());
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);
}
}

View File

@ -8,7 +8,7 @@ package io.opentelemetry.exporter.internal.okhttp;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.exporter.internal.ExporterBuilderUtil;
import io.opentelemetry.exporter.internal.TlsUtil;
import io.opentelemetry.exporter.internal.TlsConfigHelper;
import io.opentelemetry.exporter.internal.auth.Authenticator;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
import io.opentelemetry.exporter.internal.retry.RetryInterceptor;
@ -18,7 +18,7 @@ import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509KeyManager;
import javax.net.ssl.X509TrustManager;
import okhttp3.Headers;
@ -44,9 +44,8 @@ public final class OkHttpExporterBuilder<T extends Marshaler> {
private boolean compressionEnabled = false;
private boolean exportAsJson = false;
@Nullable private Headers.Builder headersBuilder;
@Nullable private byte[] trustedCertificatesPem;
@Nullable private byte[] privateKeyPem;
@Nullable private byte[] certificatePem;
private final TlsConfigHelper tlsConfigHelper = new TlsConfigHelper();
@Nullable private RetryPolicy retryPolicy;
private Supplier<MeterProvider> meterProviderSupplier = GlobalOpenTelemetry::getMeterProvider;
@Nullable private Authenticator authenticator;
@ -91,14 +90,28 @@ public final class OkHttpExporterBuilder<T extends Marshaler> {
return this;
}
public OkHttpExporterBuilder<T> setTrustedCertificates(byte[] trustedCertificatesPem) {
this.trustedCertificatesPem = trustedCertificatesPem;
public OkHttpExporterBuilder<T> configureTrustManager(byte[] trustedCertificatesPem) {
tlsConfigHelper.createTrustManager(trustedCertificatesPem);
return this;
}
public OkHttpExporterBuilder<T> setClientTls(byte[] privateKeyPem, byte[] certificatePem) {
this.privateKeyPem = privateKeyPem;
this.certificatePem = certificatePem;
public OkHttpExporterBuilder<T> setTrustManager(X509TrustManager trustManager) {
tlsConfigHelper.setTrustManager(trustManager);
return this;
}
public OkHttpExporterBuilder<T> configureKeyManager(byte[] privateKeyPem, byte[] certificatePem) {
tlsConfigHelper.createKeyManager(privateKeyPem, certificatePem);
return this;
}
public OkHttpExporterBuilder<T> setKeyManager(X509KeyManager keyManager) {
tlsConfigHelper.setKeyManager(keyManager);
return this;
}
public OkHttpExporterBuilder<T> setSslSocketFactory(SSLSocketFactory sslSocketFactory) {
tlsConfigHelper.setSslSocketFactory(sslSocketFactory);
return this;
}
@ -123,21 +136,7 @@ public final class OkHttpExporterBuilder<T extends Marshaler> {
.dispatcher(OkHttpUtil.newDispatcher())
.callTimeout(Duration.ofNanos(timeoutNanos));
if (trustedCertificatesPem != null) {
try {
X509TrustManager trustManager = TlsUtil.trustManager(trustedCertificatesPem);
X509KeyManager keyManager = null;
if (privateKeyPem != null && certificatePem != null) {
keyManager = TlsUtil.keyManager(privateKeyPem, certificatePem);
}
clientBuilder.sslSocketFactory(
TlsUtil.sslSocketFactory(keyManager, trustManager), trustManager);
} catch (SSLException e) {
throw new IllegalStateException(
"Could not set trusted certificate for OTLP HTTP connection, are they valid X.509 in PEM format?",
e);
}
}
tlsConfigHelper.configureWithSocketFactory(clientBuilder::sslSocketFactory);
Headers headers = headersBuilder == null ? null : headersBuilder.build();

View File

@ -0,0 +1,201 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
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 com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension;
import io.github.netmikey.logunit.api.LogCapturer;
import java.util.concurrent.atomic.AtomicReference;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509KeyManager;
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)
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());
});
}
@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<X509TrustManager> seenTrustManager = new AtomicReference<>();
AtomicReference<SSLSocketFactory> 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());
}
@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);
}
@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");
});
});
}
@Test
void configureWithKeyManagerHappyPath() {
AtomicReference<X509TrustManager> seenTrustManager = new AtomicReference<>();
AtomicReference<X509KeyManager> 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);
}
@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");
}));
}
@Test
void configureWithKeyManagerNoTrustManager() {
TlsConfigHelper helper = new TlsConfigHelper(tlsUtil);
helper.configureWithKeyManager(
(tm, km) -> {
fail();
});
verifyNoInteractions(tlsUtil);
}
@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);
helper.configureWithKeyManager((tm, km) -> assertSame(km, keyManager2));
logs.assertContains("Previous X509 Key manager is being replaced");
}
@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");
}
}

View File

@ -109,13 +109,13 @@ public final class JaegerGrpcSpanExporterBuilder {
* use the system default trusted certificates.
*/
public JaegerGrpcSpanExporterBuilder setTrustedCertificates(byte[] trustedCertificatesPem) {
delegate.setTrustedCertificates(trustedCertificatesPem);
delegate.configureTrustManager(trustedCertificatesPem);
return this;
}
/** Sets the client key and chain to use for verifying servers when mTLS is enabled. */
public JaegerGrpcSpanExporterBuilder setClientTls(byte[] privateKeyPem, byte[] certificatePem) {
delegate.setClientTls(privateKeyPem, certificatePem);
delegate.configureKeyManager(privateKeyPem, certificatePem);
return this;
}

View File

@ -14,6 +14,7 @@ import com.google.protobuf.InvalidProtocolBufferException;
import com.linecorp.armeria.server.ServerBuilder;
import com.linecorp.armeria.server.ServiceRequestContext;
import com.linecorp.armeria.server.grpc.protocol.AbstractUnaryGrpcService;
import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension;
import com.linecorp.armeria.testing.junit5.server.ServerExtension;
import io.github.netmikey.logunit.api.LogCapturer;
import io.opentelemetry.api.common.AttributeKey;
@ -39,7 +40,6 @@ import io.opentelemetry.sdk.trace.data.StatusData;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.net.InetAddress;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -88,6 +88,12 @@ class JaegerGrpcSpanExporterTest {
@RegisterExtension
LogCapturer logs = LogCapturer.create().captureForType(OkHttpGrpcExporter.class);
@RegisterExtension
static final SelfSignedCertificateExtension serverTls = new SelfSignedCertificateExtension();
@RegisterExtension
static final SelfSignedCertificateExtension clientTls = new SelfSignedCertificateExtension();
private static JaegerGrpcSpanExporter exporter;
@BeforeAll
@ -285,22 +291,21 @@ class JaegerGrpcSpanExporterTest {
}
@Test
void validTrustedConfig() {
void validTrustedConfig() throws Exception {
assertThatCode(
() ->
JaegerGrpcSpanExporter.builder()
.setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8)))
.setTrustedCertificates(serverTls.certificate().getEncoded()))
.doesNotThrowAnyException();
}
@Test
void validClientKeyConfig() {
void validClientKeyConfig() throws Exception {
assertThatCode(
() ->
JaegerGrpcSpanExporter.builder()
.setClientTls(
"foobar".getBytes(StandardCharsets.UTF_8),
"foobar".getBytes(StandardCharsets.UTF_8)))
clientTls.privateKey().getEncoded(), serverTls.certificate().getEncoded()))
.doesNotThrowAnyException();
}

View File

@ -99,7 +99,7 @@ public final class OtlpHttpMetricExporterBuilder {
* use the system default trusted certificates.
*/
public OtlpHttpMetricExporterBuilder setTrustedCertificates(byte[] trustedCertificatesPem) {
delegate.setTrustedCertificates(trustedCertificatesPem);
delegate.configureTrustManager(trustedCertificatesPem);
return this;
}
@ -108,7 +108,7 @@ public final class OtlpHttpMetricExporterBuilder {
* The key must be PKCS8, and both must be in PEM format.
*/
public OtlpHttpMetricExporterBuilder setClientTls(byte[] privateKeyPem, byte[] certificatePem) {
delegate.setClientTls(privateKeyPem, certificatePem);
delegate.configureKeyManager(privateKeyPem, certificatePem);
return this;
}

View File

@ -87,7 +87,7 @@ public final class OtlpHttpSpanExporterBuilder {
* use the system default trusted certificates.
*/
public OtlpHttpSpanExporterBuilder setTrustedCertificates(byte[] trustedCertificatesPem) {
delegate.setTrustedCertificates(trustedCertificatesPem);
delegate.configureTrustManager(trustedCertificatesPem);
return this;
}
@ -96,7 +96,7 @@ public final class OtlpHttpSpanExporterBuilder {
* The key must be PKCS8, and both must be in PEM format.
*/
public OtlpHttpSpanExporterBuilder setClientTls(byte[] privateKeyPem, byte[] certificatePem) {
delegate.setClientTls(privateKeyPem, certificatePem);
delegate.configureKeyManager(privateKeyPem, certificatePem);
return this;
}

View File

@ -131,7 +131,7 @@ public final class OtlpGrpcMetricExporterBuilder {
* use the system default trusted certificates.
*/
public OtlpGrpcMetricExporterBuilder setTrustedCertificates(byte[] trustedCertificatesPem) {
delegate.setTrustedCertificates(trustedCertificatesPem);
delegate.configureTrustManager(trustedCertificatesPem);
return this;
}
@ -140,7 +140,7 @@ public final class OtlpGrpcMetricExporterBuilder {
* The key must be PKCS8, and both must be in PEM format.
*/
public OtlpGrpcMetricExporterBuilder setClientTls(byte[] privateKeyPem, byte[] certificatePem) {
delegate.setClientTls(privateKeyPem, certificatePem);
delegate.configureKeyManager(privateKeyPem, certificatePem);
return this;
}

View File

@ -115,7 +115,7 @@ public final class OtlpGrpcSpanExporterBuilder {
* use the system default trusted certificates.
*/
public OtlpGrpcSpanExporterBuilder setTrustedCertificates(byte[] trustedCertificatesPem) {
delegate.setTrustedCertificates(trustedCertificatesPem);
delegate.configureTrustManager(trustedCertificatesPem);
return this;
}
@ -124,7 +124,7 @@ public final class OtlpGrpcSpanExporterBuilder {
* The key must be PKCS8, and both must be in PEM format.
*/
public OtlpGrpcSpanExporterBuilder setClientTls(byte[] privateKeyPem, byte[] certificatePem) {
delegate.setClientTls(privateKeyPem, certificatePem);
delegate.configureKeyManager(privateKeyPem, certificatePem);
return this;
}

View File

@ -83,7 +83,7 @@ public final class OtlpHttpLogRecordExporterBuilder {
* use the system default trusted certificates.
*/
public OtlpHttpLogRecordExporterBuilder setTrustedCertificates(byte[] trustedCertificatesPem) {
delegate.setTrustedCertificates(trustedCertificatesPem);
delegate.configureTrustManager(trustedCertificatesPem);
return this;
}
@ -93,7 +93,7 @@ public final class OtlpHttpLogRecordExporterBuilder {
*/
public OtlpHttpLogRecordExporterBuilder setClientTls(
byte[] privateKeyPem, byte[] certificatePem) {
delegate.setClientTls(privateKeyPem, certificatePem);
delegate.configureKeyManager(privateKeyPem, certificatePem);
return this;
}

View File

@ -115,7 +115,7 @@ public final class OtlpGrpcLogRecordExporterBuilder {
* use the system default trusted certificates.
*/
public OtlpGrpcLogRecordExporterBuilder setTrustedCertificates(byte[] trustedCertificatesPem) {
delegate.setTrustedCertificates(trustedCertificatesPem);
delegate.configureTrustManager(trustedCertificatesPem);
return this;
}
@ -125,7 +125,7 @@ public final class OtlpGrpcLogRecordExporterBuilder {
*/
public OtlpGrpcLogRecordExporterBuilder setClientTls(
byte[] privateKeyPem, byte[] certificatePem) {
delegate.setClientTls(privateKeyPem, certificatePem);
delegate.configureKeyManager(privateKeyPem, certificatePem);
return this;
}

View File

@ -304,8 +304,10 @@ public abstract class AbstractGrpcTelemetryExporterTest<T, U extends Message> {
void tls() throws Exception {
TelemetryExporter<T> exporter =
exporterBuilder()
.setEndpoint(server.httpsUri().toString())
.setTrustedCertificates(Files.readAllBytes(certificate.certificateFile().toPath()))
.setClientTls(
certificate.privateKey().getEncoded(), certificate.certificate().getEncoded())
.setEndpoint(server.httpsUri().toString())
.build();
try {
CompletableResultCode result =
@ -337,10 +339,9 @@ public abstract class AbstractGrpcTelemetryExporterTest<T, U extends Message> {
() ->
exporterBuilder()
.setEndpoint(server.httpsUri().toString())
.setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8))
.build())
.setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8)))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Could not set trusted certificates");
.hasMessageContaining("Error creating X509TrustManager with provided certs.");
}
@ParameterizedTest
@ -652,8 +653,7 @@ public abstract class AbstractGrpcTelemetryExporterTest<T, U extends Message> {
.doesNotThrowAnyException();
assertThatCode(
() ->
exporterBuilder().setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8)))
() -> exporterBuilder().setTrustedCertificates(certificate.certificate().getEncoded()))
.doesNotThrowAnyException();
}

View File

@ -368,10 +368,9 @@ public abstract class AbstractHttpTelemetryExporterTest<T, U extends Message> {
() ->
exporterBuilder()
.setEndpoint(server.httpsUri() + path)
.setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8))
.build())
.setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8)))
.isInstanceOf(IllegalStateException.class)
.hasMessageContaining("Could not set trusted certificate");
.hasMessageContaining("Error creating X509TrustManager with provided certs");
}
@ParameterizedTest
@ -555,8 +554,7 @@ public abstract class AbstractHttpTelemetryExporterTest<T, U extends Message> {
.doesNotThrowAnyException();
assertThatCode(
() ->
exporterBuilder().setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8)))
() -> exporterBuilder().setTrustedCertificates(certificate.certificate().getEncoded()))
.doesNotThrowAnyException();
}

View File

@ -9,6 +9,7 @@ import static java.util.Objects.requireNonNull;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.opentelemetry.exporter.internal.TlsConfigHelper;
import io.opentelemetry.exporter.internal.grpc.ManagedChannelUtil;
import io.opentelemetry.exporter.internal.otlp.OtlpUserAgent;
import io.opentelemetry.exporter.internal.retry.RetryPolicy;
@ -18,7 +19,6 @@ import java.time.Duration;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.net.ssl.SSLException;
/**
* Wraps a {@link TelemetryExporterBuilder}, delegating methods to upstream gRPC's {@link
@ -40,9 +40,7 @@ public final class ManagedChannelTelemetryExporterBuilder<T>
@Nullable private ManagedChannelBuilder<?> channelBuilder;
@Nullable private byte[] privateKeyPem;
@Nullable private byte[] certificatePem;
@Nullable private byte[] trustedCertificatesPem;
private final TlsConfigHelper tlsConfigHelper = new TlsConfigHelper();
@Override
public TelemetryExporterBuilder<T> setEndpoint(String endpoint) {
@ -89,14 +87,13 @@ public final class ManagedChannelTelemetryExporterBuilder<T>
@Override
public TelemetryExporterBuilder<T> setTrustedCertificates(byte[] certificates) {
this.trustedCertificatesPem = certificates;
tlsConfigHelper.createTrustManager(certificates);
return this;
}
@Override
public TelemetryExporterBuilder<T> setClientTls(byte[] privateKeyPem, byte[] certificatePem) {
this.privateKeyPem = privateKeyPem;
this.certificatePem = certificatePem;
tlsConfigHelper.createKeyManager(privateKeyPem, certificatePem);
return this;
}
@ -126,15 +123,13 @@ public final class ManagedChannelTelemetryExporterBuilder<T>
@Override
public TelemetryExporter<T> build() {
requireNonNull(channelBuilder, "channel");
if (trustedCertificatesPem != null) {
try {
ManagedChannelUtil.setClientKeysAndTrustedCertificatesPem(
channelBuilder, privateKeyPem, certificatePem, trustedCertificatesPem);
} catch (SSLException e) {
throw new IllegalStateException(
"Could not set trusted certificates, are they valid X.509 in PEM format?", e);
}
}
tlsConfigHelper.configureWithKeyManager(
(tm, km) -> {
requireNonNull(channelBuilder, "channel");
ManagedChannelUtil.setClientKeysAndTrustedCertificatesPem(channelBuilder, tm, km);
});
ManagedChannel channel = channelBuilder.build();
delegate.setChannel(channel);
TelemetryExporter<T> delegateExporter = delegate.build();

View File

@ -16,6 +16,7 @@ import io.grpc.ManagedChannelBuilder;
import io.grpc.Metadata;
import io.grpc.stub.MetadataUtils;
import io.opentelemetry.exporter.internal.ExporterBuilderUtil;
import io.opentelemetry.exporter.internal.TlsConfigHelper;
import io.opentelemetry.exporter.internal.grpc.ManagedChannelUtil;
import io.opentelemetry.exporter.internal.grpc.MarshalerServiceStub;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
@ -25,7 +26,6 @@ import java.time.Duration;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import javax.annotation.Nullable;
import javax.net.ssl.SSLException;
final class DefaultGrpcServiceBuilder<ReqT extends Marshaler, ResT extends UnMarshaler>
implements GrpcServiceBuilder<ReqT, ResT> {
@ -39,10 +39,8 @@ final class DefaultGrpcServiceBuilder<ReqT extends Marshaler, ResT extends UnMar
private URI endpoint;
private boolean compressionEnabled = false;
@Nullable private Metadata metadata;
@Nullable private byte[] trustedCertificatesPem;
@Nullable private byte[] privateKeyPem;
@Nullable private byte[] certificatePem;
@Nullable private RetryPolicy retryPolicy;
private final TlsConfigHelper tlsConfigHelper = new TlsConfigHelper();
// Visible for testing
DefaultGrpcServiceBuilder(
@ -101,14 +99,13 @@ final class DefaultGrpcServiceBuilder<ReqT extends Marshaler, ResT extends UnMar
public DefaultGrpcServiceBuilder<ReqT, ResT> setTrustedCertificates(
byte[] trustedCertificatesPem) {
requireNonNull(trustedCertificatesPem, "trustedCertificatesPem");
this.trustedCertificatesPem = trustedCertificatesPem;
tlsConfigHelper.createTrustManager(trustedCertificatesPem);
return this;
}
@Override
public GrpcServiceBuilder<ReqT, ResT> setClientTls(byte[] privateKeyPem, byte[] certificatePem) {
this.privateKeyPem = privateKeyPem;
this.certificatePem = certificatePem;
tlsConfigHelper.createKeyManager(privateKeyPem, certificatePem);
return this;
}
@ -147,17 +144,10 @@ final class DefaultGrpcServiceBuilder<ReqT extends Marshaler, ResT extends UnMar
managedChannelBuilder.intercept(MetadataUtils.newAttachHeadersInterceptor(metadata));
}
if (trustedCertificatesPem != null) {
try {
ManagedChannelUtil.setClientKeysAndTrustedCertificatesPem(
managedChannelBuilder, privateKeyPem, certificatePem, trustedCertificatesPem);
} catch (SSLException e) {
throw new IllegalStateException(
"Could not set trusted certificates for gRPC TLS connection, are they valid "
+ "X.509 in PEM format?",
e);
}
}
tlsConfigHelper.configureWithKeyManager(
(tm, km) ->
ManagedChannelUtil.setClientKeysAndTrustedCertificatesPem(
managedChannelBuilder, tm, km));
if (retryPolicy != null) {
managedChannelBuilder.defaultServiceConfig(toServiceConfig(grpcServiceName, retryPolicy));

View File

@ -8,15 +8,19 @@ package io.opentelemetry.sdk.extension.trace.jaeger.sampler;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.linecorp.armeria.testing.junit5.server.SelfSignedCertificateExtension;
import io.opentelemetry.exporter.internal.retry.RetryPolicy;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
class DefaultGrpcServiceBuilderTest {
@RegisterExtension
static final SelfSignedCertificateExtension serverTls = new SelfSignedCertificateExtension();
private static DefaultGrpcServiceBuilder<
SamplingStrategyParametersMarshaler, SamplingStrategyResponseUnMarshaler>
exporterBuilder() {
@ -52,8 +56,7 @@ class DefaultGrpcServiceBuilderTest {
.doesNotThrowAnyException();
assertThatCode(
() ->
exporterBuilder().setTrustedCertificates("foobar".getBytes(StandardCharsets.UTF_8)))
() -> exporterBuilder().setTrustedCertificates(serverTls.certificate().getEncoded()))
.doesNotThrowAnyException();
assertThatCode(() -> exporterBuilder().addRetryPolicy(RetryPolicy.getDefault()))