Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add skaffolding for low level exporter SSL API #5362

Merged
merged 2 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 #setTrustManagerFromCerts(byte[])} and {@link
* #setKeyManagerFromCerts(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()}.
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
*
* <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.
Comment on lines +42 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the better option would be to have multiple constructors with the various argument permutations. That would result in multiple calls being independent instances and not require explicit callout.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option would be to have these as static methods that return the constructed object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah, this is an internal class. Think of it like a stateful builder, or really a collaborator for a stateful builder. I think a bunch of overloaded constructors is a muddier option in this case.

*
* @param trustedCertsPem Certificate in PEM format.
* @return this
*/
public TlsConfigHelper createTrustManager(byte[] trustedCertsPem) {
public void setTrustManagerFromCerts(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 setKeyManagerFromCerts(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,50 @@ 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 #setTrustManagerFromCerts(byte[])} or {@link
* #setKeyManagerFromCerts(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.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(
jack-berg marked this conversation as resolved.
Show resolved Hide resolved
@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 @@ -98,13 +100,14 @@ public GrpcExporterBuilder<T> setCompression(String compressionMethod) {
return this;
}

public GrpcExporterBuilder<T> configureTrustManager(byte[] trustedCertificatesPem) {
tlsConfigHelper.createTrustManager(trustedCertificatesPem);
public GrpcExporterBuilder<T> setTrustManagerFromCerts(byte[] trustedCertificatesPem) {
tlsConfigHelper.setTrustManagerFromCerts(trustedCertificatesPem);
return this;
}

public GrpcExporterBuilder<T> configureKeyManager(byte[] privateKeyPem, byte[] certificatePem) {
tlsConfigHelper.createKeyManager(privateKeyPem, certificatePem);
public GrpcExporterBuilder<T> setKeyManagerFromCerts(
byte[] privateKeyPem, byte[] certificatePem) {
tlsConfigHelper.setKeyManagerFromCerts(privateKeyPem, certificatePem);
return this;
}

Expand Down Expand Up @@ -133,7 +136,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