Skip to content

Commit

Permalink
Add skaffolding for low level exporter SSL API
Browse files Browse the repository at this point in the history
  • Loading branch information
jack-berg committed Apr 7, 2023
1 parent e1fcd15 commit 7430348
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 400 deletions.
3 changes: 0 additions & 3 deletions exporters/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()}.
*
* <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;
@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.
*
* <p>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.
*
* <p>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?",
Expand All @@ -86,112 +83,51 @@ public TlsConfigHelper createKeyManager(byte[] privateKeyPem, byte[] certificate
}

/**
* Assigns the X509KeyManager.
* Configure the {@link SSLContext} and {@link X509TrustManager}.
*
* <p>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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -133,7 +135,11 @@ public GrpcExporter<T> 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://")) {
Expand Down
Loading

0 comments on commit 7430348

Please sign in to comment.