From dfd71ebb38b6d1a74b0888675903c5cc34e56819 Mon Sep 17 00:00:00 2001 From: Jack Berg Date: Thu, 2 Jan 2025 14:07:00 -0600 Subject: [PATCH] Add OTLP authentication section to ./java/sdk.md --- content-modules/opentelemetry-java-examples | 2 +- content/en/docs/languages/java/sdk.md | 141 ++++++++++++++++++-- 2 files changed, 131 insertions(+), 12 deletions(-) diff --git a/content-modules/opentelemetry-java-examples b/content-modules/opentelemetry-java-examples index 63cc9b44310f..e2120cea29fc 160000 --- a/content-modules/opentelemetry-java-examples +++ b/content-modules/opentelemetry-java-examples @@ -1 +1 @@ -Subproject commit 63cc9b44310f1ac2e7b37e6402fe48e9e0efe3ef +Subproject commit e2120cea29fcdd4bc0c2475a857862b3d8cbb565 diff --git a/content/en/docs/languages/java/sdk.md b/content/en/docs/languages/java/sdk.md index cf7b49074622..de39c01edec0 100644 --- a/content/en/docs/languages/java/sdk.md +++ b/content/en/docs/languages/java/sdk.md @@ -435,8 +435,7 @@ Span exporters built-in to the SDK and maintained by the community in | `InterceptableSpanExporter` | `io.opentelemetry.contrib:opentelemetry-processors:{{% param vers.contrib %}}-alpha` | Passes spans to a flexible interceptor before exporting. | | `KafkaSpanExporter` | `io.opentelemetry.contrib:opentelemetry-kafka-exporter:{{% param vers.contrib %}}-alpha` | Exports spans by writing to a Kafka topic. | -**[1]**: See [OTLP exporter sender](#otlp-exporter-senders) for implementation -details. +**[1]**: See [OTLP exporters](#otlp-exporters) for implementation details. The following code snippet demonstrates `SpanExporter` programmatic configuration: @@ -767,8 +766,7 @@ Metric exporters built-in to the SDK and maintained by the community in | `OtlpStdoutMetricExporter` | `io.opentelemetry:opentelemetry-exporter-logging-otlp:{{% param vers.otel %}}` | Logs metrics to `System.out` in the OTLP [JSON file encoding][] (experimental). | | `InterceptableMetricExporter` | `io.opentelemetry.contrib:opentelemetry-processors:{{% param vers.contrib %}}-alpha` | Passes metrics to a flexible interceptor before exporting. | -**[1]**: See [OTLP exporter sender](#otlp-exporter-senders) for implementation -details. +**[1]**: See [OTLP exporters](#otlp-exporters) for implementation details. The following code snippet demonstrates `MetricExporter` programmatic configuration: @@ -1095,8 +1093,7 @@ Span exporters built-in to the SDK and maintained by the community in | `OtlpStdoutLogRecordExporter` | `io.opentelemetry:opentelemetry-exporter-logging-otlp:{{% param vers.otel %}}` | Logs log records to `System.out` in the OTLP [JSON file encoding][] (experimental). | | `InterceptableLogRecordExporter` | `io.opentelemetry.contrib:opentelemetry-processors:{{% param vers.contrib %}}-alpha` | Passes log records to a flexible interceptor before exporting. | -**[1]**: See [OTLP exporter sender](#otlp-exporter-senders) for implementation -details. +**[1]**: See [OTLP exporters](#otlp-exporters) for implementation details. **[2]**: `OtlpJsonLoggingLogRecordExporter` logs to JUL, and may cause infinite loops (i.e. JUL -> SLF4J -> Logback -> OpenTelemetry Appender -> OpenTelemetry @@ -1362,7 +1359,7 @@ public class IgnoreExportErrorsFilter implements java.util.logging.Filter { io.opentelemetry.sdk.trace.export.BatchSpanProcessor = io.opentelemetry.extension.logging.IgnoreExportErrorsFilter ``` -### OTLP exporter senders +### OTLP exporters The [span exporter](#spanexporter), [metric exporter](#metricexporter), and [log exporter](#logrecordexporter) discuss OTLP exporters of the form: @@ -1371,11 +1368,21 @@ The [span exporter](#spanexporter), [metric exporter](#metricexporter), and - `OtlpGrpc{Signal}Exporter`s export data via OTLP `grpc`. The exporters for all signals are available via -`io.opentelemetry:opentelemetry-exporter-otlp:{{% param vers.otel %}}`. +`io.opentelemetry:opentelemetry-exporter-otlp:{{% param vers.otel %}}`, and have +significant overlap across `grpc` and `http/protobuf` versions of the OTLP +protocol, and between signals. The following sections elaborate on certain key +concepts: -Internally, these exporters depend on various client libraries to execute HTTP -and gRPC requests. There is no single HTTP / gRPC client library which satisfies -all use cases in the Java ecosystem: +- [Senders](#senders): The sender abstraction allows for a different HTTP / gRPC + client libraries, catering to various use cases. +- [Authentication](#authentication): Discusses authentication options for OTLP + exporters. + +#### Senders + +The OTLP exporters depend on various client libraries to execute HTTP and gRPC +requests. There is no single HTTP / gRPC client library which satisfies all use +cases in the Java ecosystem: - Java 11+ brings the built-in `java.net.http.HttpClient`, but `opentelemetry-java` needs to support Java 8+ users, and this can't be used to @@ -1403,6 +1410,118 @@ add a dependency on the alternative. you must also add a dependency on a [gRPC transport implementations](https://github.com/grpc/grpc-java#transport). +#### Authentication + +The OTLP exporters provide mechanisms for static and dynamic header-based +authentication, and for mTLS. + +If using +[zero-code SDK autoconfigure](../configuration/#zero-code-sdk-autoconfigure) +with environment variables and system properties, see +[relevant properties](../configuration/#properties-exporters): + +- `otel.exporter.otlp.headers` for static header-based authentication. +- `otel.exporter.otlp.client.key`, `otel.exporter.otlp.client.certificate` for + mTLS authentication. + +The following code snippet demonstrates programmatic configuration of static and +dynamic header-based authentication: + + + +```java +package otel; + +import io.opentelemetry.exporter.otlp.http.logs.OtlpHttpLogRecordExporter; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; +import java.time.Duration; +import java.time.Instant; +import java.util.Collections; +import java.util.Map; +import java.util.function.Supplier; + +public class OtlpAuthenticationConfig { + public static void staticAuthenticationHeader(String endpoint) { + // If the OTLP destination accepts a static, long-lived authentication header like an API key, + // set it as a header. + // This reads the API key from the OTLP_API_KEY env var to avoid hard coding the secret in + // source code. + String apiKeyHeaderName = "api-key"; + String apiKeyHeaderValue = System.getenv("OTLP_API_KEY"); + + // Initialize OTLP Span, Metric, and LogRecord exporters using a similar pattern + OtlpHttpSpanExporter spanExporter = + OtlpHttpSpanExporter.builder() + .setEndpoint(endpoint) + .addHeader(apiKeyHeaderName, apiKeyHeaderValue) + .build(); + OtlpHttpMetricExporter metricExporter = + OtlpHttpMetricExporter.builder() + .setEndpoint(endpoint) + .addHeader(apiKeyHeaderName, apiKeyHeaderValue) + .build(); + OtlpHttpLogRecordExporter logRecordExporter = + OtlpHttpLogRecordExporter.builder() + .setEndpoint(endpoint) + .addHeader(apiKeyHeaderName, apiKeyHeaderValue) + .build(); + } + + public static void dynamicAuthenticationHeader(String endpoint) { + // If the OTLP destination requires a dynamic authentication header, such as a JWT which needs + // to be periodically refreshed, use a header supplier. + // Here we implement a simple supplier which adds a header of the form "Authorization: Bearer + // ", where is fetched from refreshBearerToken every 10 minutes. + String username = System.getenv("OTLP_USERNAME"); + String password = System.getenv("OTLP_PASSWORD"); + Supplier> supplier = + new AuthHeaderSupplier(() -> refreshToken(username, password), Duration.ofMinutes(10)); + + // Initialize OTLP Span, Metric, and LogRecord exporters using a similar pattern + OtlpHttpSpanExporter spanExporter = + OtlpHttpSpanExporter.builder().setEndpoint(endpoint).setHeaders(supplier).build(); + OtlpHttpMetricExporter metricExporter = + OtlpHttpMetricExporter.builder().setEndpoint(endpoint).setHeaders(supplier).build(); + OtlpHttpLogRecordExporter logRecordExporter = + OtlpHttpLogRecordExporter.builder().setEndpoint(endpoint).setHeaders(supplier).build(); + } + + private static class AuthHeaderSupplier implements Supplier> { + private final Supplier tokenRefresher; + private final Duration tokenRefreshInterval; + private Instant refreshedAt = Instant.ofEpochMilli(0); + private String currentTokenValue; + + private AuthHeaderSupplier(Supplier tokenRefresher, Duration tokenRefreshInterval) { + this.tokenRefresher = tokenRefresher; + this.tokenRefreshInterval = tokenRefreshInterval; + } + + @Override + public Map get() { + return Collections.singletonMap("Authorization", "Bearer " + getToken()); + } + + private synchronized String getToken() { + Instant now = Instant.now(); + if (currentTokenValue == null || now.isAfter(refreshedAt.plus(tokenRefreshInterval))) { + currentTokenValue = tokenRefresher.get(); + refreshedAt = now; + } + return currentTokenValue; + } + } + + private static String refreshToken(String username, String password) { + // For a production scenario, this would be replaced with out-of-band request to exchange + // username / password for bearer token. + return "abc123"; + } +} +``` + + ### Testing TODO: document tools available for testing the SDK