diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt index df26146497b..0c8f8d0b068 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-exporter-zipkin.txt @@ -1,2 +1,5 @@ Comparing source compatibility of against -No changes. \ No newline at end of file +*** MODIFIED CLASS: PUBLIC FINAL io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder (not serializable) + === CLASS FILE FORMAT VERSION: 52.0 <- 52.0 + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder setClientTls(byte[], byte[]) + +++ NEW METHOD: PUBLIC(+) io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder setTrustedCertificates(byte[]) diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java index c47745b1351..ae1b0354592 100644 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java +++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/ZipkinSpanExporterBuilder.java @@ -10,11 +10,15 @@ import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.metrics.MeterProvider; +import io.opentelemetry.exporter.internal.TlsUtil; import java.net.InetAddress; 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.X509KeyManager; +import javax.net.ssl.X509TrustManager; import zipkin2.Span; import zipkin2.codec.BytesEncoder; import zipkin2.codec.SpanBytesEncoder; @@ -27,11 +31,20 @@ public final class ZipkinSpanExporterBuilder { private Supplier localIpAddressSupplier = LocalInetAddressSupplier.getInstance(); @Nullable private Sender sender; private String endpoint = ZipkinSpanExporter.DEFAULT_ENDPOINT; - // compression is enabled by default, because this is the default of OkHttpSender, + // compression is enabled by default, because this is the default of + // OkHttpSender, // which is created when no custom sender is set (see OkHttpSender.Builder) private boolean compressionEnabled = true; private long readTimeoutMillis = TimeUnit.SECONDS.toMillis(10); private Supplier meterProviderSupplier = GlobalOpenTelemetry::getMeterProvider; + private final OkHttpSender.Builder okHttpSenderBuilder; + @Nullable private byte[] trustedCertificatesPem; + @Nullable private byte[] privateKeyPem; + @Nullable private byte[] certificatePem; + + public ZipkinSpanExporterBuilder() { + this.okHttpSenderBuilder = OkHttpSender.newBuilder(); + } /** * Sets the Zipkin sender. Implements the client side of the span transport. An {@link @@ -151,6 +164,29 @@ public ZipkinSpanExporterBuilder setMeterProvider(MeterProvider meterProvider) { return this; } + /** + * Sets the certificate chain to use for verifying servers when TLS is enabled. The {@code byte[]} + * should contain an X.509 certificate collection in PEM format. If not set, TLS connections will + * use the system default trusted certificates. + */ + public ZipkinSpanExporterBuilder setTrustedCertificates(byte[] trustedCertificatesPem) { + requireNonNull(trustedCertificatesPem, "trustedCertificatesPem"); + this.trustedCertificatesPem = trustedCertificatesPem; + return this; + } + + /** + * Sets ths client key and the certificate chain to use for verifying client when TLS is enabled. + * The key must be PKCS8, and both must be in PEM format. + */ + public ZipkinSpanExporterBuilder setClientTls(byte[] privateKeyPem, byte[] certificatePem) { + requireNonNull(privateKeyPem, "privateKeyPem"); + requireNonNull(certificatePem, "certificatePem"); + this.privateKeyPem = privateKeyPem; + this.certificatePem = certificatePem; + return this; + } + /** * Builds a {@link ZipkinSpanExporter}. * @@ -159,8 +195,25 @@ public ZipkinSpanExporterBuilder setMeterProvider(MeterProvider meterProvider) { public ZipkinSpanExporter build() { Sender sender = this.sender; if (sender == null) { + if (trustedCertificatesPem != null) { + try { + X509TrustManager trustManager = TlsUtil.trustManager(trustedCertificatesPem); + X509KeyManager keyManager = null; + if (privateKeyPem != null && certificatePem != null) { + keyManager = TlsUtil.keyManager(privateKeyPem, certificatePem); + } + this.okHttpSenderBuilder + .clientBuilder() + .sslSocketFactory(TlsUtil.sslSocketFactory(keyManager, trustManager), trustManager); + } catch (SSLException e) { + throw new IllegalStateException( + "Could not set trusted certificate for Zipkin HTTP connection, are they valid X.509 in PEM format?", + e); + } + } + sender = - OkHttpSender.newBuilder() + this.okHttpSenderBuilder .endpoint(endpoint) .compressionEnabled(compressionEnabled) .readTimeout((int) readTimeoutMillis) diff --git a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProvider.java b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProvider.java index cbd55d386dc..271cee10f01 100644 --- a/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProvider.java +++ b/exporters/zipkin/src/main/java/io/opentelemetry/exporter/zipkin/internal/ZipkinSpanExporterProvider.java @@ -8,9 +8,14 @@ import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; import io.opentelemetry.exporter.zipkin.ZipkinSpanExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; import java.time.Duration; +import javax.annotation.Nullable; /** * {@link SpanExporter} SPI implementation for {@link ZipkinSpanExporter}. @@ -38,6 +43,47 @@ public SpanExporter createExporter(ConfigProperties config) { builder.setReadTimeout(timeout); } + String certificatePath = config.getString("otel.exporter.zipkin.certificate"); + String clientKeyPath = config.getString("otel.exporter.zipkin.client.key"); + String clientKeyChainPath = config.getString("otel.exporter.zipkin.client.certificate"); + + if (clientKeyPath != null && clientKeyChainPath == null) { + throw new ConfigurationException("Client key provided but certification chain is missing"); + } else if (clientKeyPath == null && clientKeyChainPath != null) { + throw new ConfigurationException("Client key chain provided but key is missing"); + } + + byte[] certificateBytes = readFileBytes(certificatePath); + if (certificateBytes != null) { + builder.setTrustedCertificates(certificateBytes); + } + + byte[] clientKeyBytes = readFileBytes(clientKeyPath); + byte[] clientKeyChainBytes = readFileBytes(clientKeyChainPath); + + if (clientKeyBytes != null && clientKeyChainBytes != null) { + builder.setClientTls(clientKeyBytes, clientKeyChainBytes); + } + return builder.build(); } + + @Nullable + private static byte[] readFileBytes(@Nullable String filePath) { + if (filePath == null) { + return null; + } + File file = new File(filePath); + if (!file.exists()) { + throw new ConfigurationException("Invalid Zipkin certificate/key path: " + filePath); + } + try { + RandomAccessFile raf = new RandomAccessFile(file, "r"); + byte[] bytes = new byte[(int) raf.length()]; + raf.readFully(bytes); + return bytes; + } catch (IOException e) { + throw new ConfigurationException("Error reading content of file (" + filePath + ")", e); + } + } } diff --git a/sdk-extensions/autoconfigure/README.md b/sdk-extensions/autoconfigure/README.md index 9e9b681cab3..9f3ab992578 100644 --- a/sdk-extensions/autoconfigure/README.md +++ b/sdk-extensions/autoconfigure/README.md @@ -131,10 +131,13 @@ The [Jaeger](https://www.jaegertracing.io/docs/1.21/apis/#protobuf-via-grpc-stab The [Zipkin](https://zipkin.io/zipkin-api/) exporter. It sends JSON in [Zipkin format](https://zipkin.io/zipkin-api/#/default/post_spans) to a specified HTTP URL. -| System property | Environment variable | Description | -|-------------------------------|-------------------------------|-----------------------------------------------------------------------------------------------------------------------| -| otel.traces.exporter=zipkin | OTEL_TRACES_EXPORTER=zipkin | Select the Zipkin exporter | -| otel.exporter.zipkin.endpoint | OTEL_EXPORTER_ZIPKIN_ENDPOINT | The Zipkin endpoint to connect to. Default is `http://localhost:9411/api/v2/spans`. Currently only HTTP is supported. | +| System property | Environment variable | Description | +|-----------------------------------------|-----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| otel.traces.exporter=zipkin | OTEL_TRACES_EXPORTER=zipkin | Select the Zipkin exporter | +| otel.exporter.zipkin.endpoint | OTEL_EXPORTER_ZIPKIN_ENDPOINT | The Zipkin endpoint to connect to. Default is `http://localhost:9411/api/v2/spans`. Currently only HTTP is supported. | +| otel.exporter.zipkin.certificate | OTEL_EXPORTER_ZIPKIN_CERTIFICATE | The path to the file containing trusted certificates to use when verifying a server's TLS credentials. The file should contain one or more X.509 certificates in PEM format. By default the host platform's trusted root certificates are used. | +| otel.exporter.zipkin.client.certificate | OTEL_EXPORTER_ZIPKIN_CLIENT_CERTIFICATE | The path to the file containing trusted certificates to use when verifying a client's TLS credentials. The file should contain one or more X.509 certificates in PEM format. By default no chain file is used. | +| otel.exporter.zipkin.client.key | OTEL_EXPORTER_ZIPKIN_CLIENT_KEY | The path to the file containing private client key to use when verifying a client's TLS credentials. The file should contain one private key PKCS8 PEM format. By default no client key is used. | ### Prometheus exporter