forked from open-telemetry/opentelemetry-java-contrib
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGcpAuthAutoConfigurationCustomizerProvider.java
More file actions
238 lines (223 loc) · 10.8 KB
/
GcpAuthAutoConfigurationCustomizerProvider.java
File metadata and controls
238 lines (223 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.contrib.gcp.auth;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auto.service.AutoService;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.contrib.gcp.auth.GoogleAuthException.Reason;
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter;
import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
import io.opentelemetry.sdk.metrics.export.MetricExporter;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.sdk.trace.export.SpanExporter;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
/**
* An AutoConfigurationCustomizerProvider for Google Cloud Platform (GCP) OpenTelemetry (OTLP)
* integration.
*
* <p>This class is registered as a service provider using {@link AutoService} and is responsible
* for customizing the OpenTelemetry configuration for GCP specific behavior. It retrieves Google
* Application Default Credentials (ADC) and adds them as authorization headers to the configured
* {@link SpanExporter}. It also sets default properties and resource attributes for GCP
* integration.
*
* @see AutoConfigurationCustomizerProvider
* @see GoogleCredentials
*/
@AutoService(AutoConfigurationCustomizerProvider.class)
public class GcpAuthAutoConfigurationCustomizerProvider
implements AutoConfigurationCustomizerProvider {
private static final Logger logger =
Logger.getLogger(GcpAuthAutoConfigurationCustomizerProvider.class.getName());
private static final String SIGNAL_TARGET_WARNING_FIX_SUGGESTION =
String.format(
"You may safely ignore this warning if it is intentional, otherwise please configure the '%s' by exporting valid values to environment variable: %s or by setting valid values in system property: %s.",
ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getUserReadableName(),
ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getEnvironmentVariable(),
ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty());
static final String QUOTA_USER_PROJECT_HEADER = "x-goog-user-project";
static final String GCP_USER_PROJECT_ID_KEY = "gcp.project_id";
static final String SIGNAL_TYPE_TRACES = "traces";
static final String SIGNAL_TYPE_METRICS = "metrics";
static final String SIGNAL_TYPE_ALL = "all";
/**
* Customizes the provided {@link AutoConfigurationCustomizer} such that authenticated exports to
* GCP Telemetry API are possible from the configured OTLP exporter.
*
* <p>This method attempts to retrieve Google Application Default Credentials (ADC) and performs
* the following:
*
* <ul>
* <li>Verifies whether the configured OTLP endpoint (base or signal specific) is a known GCP
* endpoint.
* <li>If the configured base OTLP endpoint is a known GCP Telemetry API endpoint, customizes
* both the configured OTLP {@link SpanExporter} and {@link MetricExporter}.
* <li>If the configured signal specific endpoint is a known GCP Telemetry API endpoint,
* customizes only the signal specific exporter.
* </ul>
*
* The 'customization' performed includes customizing the exporters by adding required headers to
* the export calls made and customizing the resource by adding required resource attributes to
* enable GCP integration.
*
* @param autoConfiguration the AutoConfigurationCustomizer to customize.
* @throws GoogleAuthException if there's an error retrieving Google Application Default
* Credentials.
* @throws io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException if required options are
* not configured through environment variables or system properties.
*/
@Override
public void customize(@Nonnull AutoConfigurationCustomizer autoConfiguration) {
GoogleCredentials credentials;
try {
credentials = GoogleCredentials.getApplicationDefault();
} catch (IOException e) {
throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e);
}
autoConfiguration
.addSpanExporterCustomizer(
(spanExporter, configProperties) ->
customizeSpanExporter(spanExporter, credentials, configProperties))
.addMetricExporterCustomizer(
(metricExporter, configProperties) ->
customizeMetricExporter(metricExporter, credentials, configProperties))
.addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource);
}
@Override
public int order() {
return Integer.MAX_VALUE - 1;
}
private static SpanExporter customizeSpanExporter(
SpanExporter exporter, GoogleCredentials credentials, ConfigProperties configProperties) {
if (isSignalTargeted(SIGNAL_TYPE_TRACES, configProperties)) {
return addAuthorizationHeaders(exporter, credentials, configProperties);
} else {
String[] params = {SIGNAL_TYPE_TRACES, SIGNAL_TARGET_WARNING_FIX_SUGGESTION};
logger.log(
Level.WARNING,
"GCP Authentication Extension is not configured for signal type: {0}. {1}",
params);
}
return exporter;
}
private static MetricExporter customizeMetricExporter(
MetricExporter exporter, GoogleCredentials credentials, ConfigProperties configProperties) {
if (isSignalTargeted(SIGNAL_TYPE_METRICS, configProperties)) {
return addAuthorizationHeaders(exporter, credentials, configProperties);
} else {
String[] params = {SIGNAL_TYPE_METRICS, SIGNAL_TARGET_WARNING_FIX_SUGGESTION};
logger.log(
Level.WARNING,
"GCP Authentication Extension is not configured for signal type: {0}. {1}",
params);
}
return exporter;
}
// Checks if the auth extension is configured to target the passed signal for authentication.
private static boolean isSignalTargeted(String checkSignal, ConfigProperties configProperties) {
String userSpecifiedTargetedSignals =
ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getConfiguredValueWithFallback(
configProperties, () -> SIGNAL_TYPE_ALL);
return Arrays.stream(userSpecifiedTargetedSignals.split(","))
.map(String::trim)
.anyMatch(
targetedSignal ->
targetedSignal.equals(checkSignal) || targetedSignal.equals(SIGNAL_TYPE_ALL));
}
// Adds authorization headers to the calls made by the OtlpGrpcSpanExporter and
// OtlpHttpSpanExporter.
private static SpanExporter addAuthorizationHeaders(
SpanExporter exporter, GoogleCredentials credentials, ConfigProperties configProperties) {
if (exporter instanceof OtlpHttpSpanExporter) {
OtlpHttpSpanExporterBuilder builder =
((OtlpHttpSpanExporter) exporter)
.toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials, configProperties));
return builder.build();
} else if (exporter instanceof OtlpGrpcSpanExporter) {
OtlpGrpcSpanExporterBuilder builder =
((OtlpGrpcSpanExporter) exporter)
.toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials, configProperties));
return builder.build();
}
return exporter;
}
// Adds authorization headers to the calls made by the OtlpGrpcMetricExporter and
// OtlpHttpMetricExporter.
private static MetricExporter addAuthorizationHeaders(
MetricExporter exporter, GoogleCredentials credentials, ConfigProperties configProperties) {
if (exporter instanceof OtlpHttpMetricExporter) {
OtlpHttpMetricExporterBuilder builder =
((OtlpHttpMetricExporter) exporter)
.toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials, configProperties));
return builder.build();
} else if (exporter instanceof OtlpGrpcMetricExporter) {
OtlpGrpcMetricExporterBuilder builder =
((OtlpGrpcMetricExporter) exporter)
.toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials, configProperties));
return builder.build();
}
return exporter;
}
private static Map<String, String> getRequiredHeaderMap(
GoogleCredentials credentials, ConfigProperties configProperties) {
Map<String, List<String>> gcpHeaders;
try {
// this also refreshes the credentials, if required
gcpHeaders = credentials.getRequestMetadata();
} catch (IOException e) {
throw new GoogleAuthException(Reason.FAILED_ADC_REFRESH, e);
}
// flatten list
Map<String, String> flattenedHeaders =
gcpHeaders.entrySet().stream()
.collect(
Collectors.toMap(
Map.Entry::getKey,
entry ->
entry.getValue().stream()
.filter(Objects::nonNull) // Filter nulls
.filter(s -> !s.isEmpty()) // Filter empty strings
.collect(Collectors.joining(","))));
// Add quota user project header if not detected by the auth library and user provided it via
// system properties.
if (!flattenedHeaders.containsKey(QUOTA_USER_PROJECT_HEADER)) {
Optional<String> maybeConfiguredQuotaProjectId =
ConfigurableOption.GOOGLE_CLOUD_QUOTA_PROJECT.getConfiguredValueAsOptional(
configProperties);
maybeConfiguredQuotaProjectId.ifPresent(
configuredQuotaProjectId ->
flattenedHeaders.put(QUOTA_USER_PROJECT_HEADER, configuredQuotaProjectId));
}
return flattenedHeaders;
}
// Updates the current resource with the attributes required for ingesting OTLP data on GCP.
private static Resource customizeResource(Resource resource, ConfigProperties configProperties) {
String gcpProjectId =
ConfigurableOption.GOOGLE_CLOUD_PROJECT.getConfiguredValue(configProperties);
Resource res =
Resource.create(
Attributes.of(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), gcpProjectId));
return resource.merge(res);
}
}