Add GCP authentication extension (#1631)
This commit is contained in:
parent
deb9746f80
commit
7480d43b1f
|
@ -33,6 +33,9 @@ components:
|
|||
gcp-resources:
|
||||
- jsuereth
|
||||
- psx95
|
||||
gcp-auth-extension:
|
||||
- jsuereth
|
||||
- psx95
|
||||
jfr-connection:
|
||||
- breedx-splk
|
||||
- jeanbisutti
|
||||
|
|
|
@ -32,6 +32,7 @@ component_names["compressors/"]="Compressors"
|
|||
component_names["consistent-sampling/"]="Consistent sampling"
|
||||
component_names["disk-buffering/"]="Disk buffering"
|
||||
component_names["gcp-resources/"]="GCP Resources"
|
||||
component_names["gcp-auth-extension/"]="GCP authentication extension"
|
||||
component_names["inferred-spans/"]="Inferred spans"
|
||||
component_names["jfr-connection/"]="JFR connection"
|
||||
component_names["jfr-events/"]="JFR events"
|
||||
|
|
|
@ -18,6 +18,7 @@ feature or via instrumentation, this project is hopefully for you.
|
|||
| alpha | [zstd Compressor](./compressors/compressor-zstd/README.md) |
|
||||
| alpha | [Consistent Sampling](./consistent-sampling/README.md) |
|
||||
| alpha | [Disk Buffering](./disk-buffering/README.md) |
|
||||
| alpha | [GCP Authentication Extension](./gcp-auth-extension/README.md) |
|
||||
| beta | [GCP Resources](./gcp-resources/README.md) |
|
||||
| beta | [Inferred Spans](./inferred-spans/README.md) |
|
||||
| alpha | [JFR Connection](./jfr-connection/README.md) |
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
# Google Cloud Authentication Extension
|
||||
|
||||
The Google Cloud Auth Extension allows the users to export telemetry from their applications to Google Cloud using the built-in OTLP exporters.\
|
||||
The extension takes care of the necessary configuration required to authenticate to GCP to successfully export telemetry.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Ensure the presence of Google Cloud Credentials on your machine/environment
|
||||
|
||||
```shell
|
||||
gcloud auth application-default login
|
||||
```
|
||||
|
||||
Executing this command will save your application credentials to default path which will depend on the type of machine -
|
||||
|
||||
- Linux, macOS: `$HOME/.config/gcloud/application_default_credentials.json`
|
||||
- Windows: `%APPDATA%\gcloud\application_default_credentials.json`
|
||||
|
||||
**NOTE: This method of authentication is not recommended for production environments.**
|
||||
|
||||
Next, export the credentials to `GOOGLE_APPLICATION_CREDENTIALS` environment variable -
|
||||
|
||||
For Linux & MacOS:
|
||||
|
||||
```shell
|
||||
export GOOGLE_APPLICATION_CREDENTIALS=$HOME/.config/gcloud/application_default_credentials.json
|
||||
```
|
||||
|
||||
These credentials are built-in running in a Google App Engine, Google Cloud Shell or Google Compute Engine environment.
|
||||
|
||||
### Configuring the extension
|
||||
|
||||
The extension can be configured either by environment variables or system properties.
|
||||
|
||||
Here is a list of configurable options for the extension:
|
||||
|
||||
- `GOOGLE_CLOUD_PROJECT`: Environment variable that represents the Google Cloud Project ID to which the telemetry needs to be exported.
|
||||
- Can also be configured using `google.cloud.project` system property.
|
||||
- If this option is not configured, the extension would infer GCP Project ID from the application default credentials. For more information on application default credentials, see [here](https://cloud.google.com/docs/authentication/application-default-credentials).
|
||||
|
||||
## Usage
|
||||
|
||||
### With OpenTelemetry Java agent
|
||||
|
||||
The OpenTelemetry Java Agent Extension can be easily added to any Java application by modifying the startup command to the application.
|
||||
For more information on Extensions, see the [documentation here](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/examples/extension/README.md).
|
||||
|
||||
Below is a snippet showing how to add the extension to a Java application using the Gradle build system.
|
||||
|
||||
```gradle
|
||||
// Specify OpenTelemetry Autoinstrumentation Java Agent Path.
|
||||
def otelAgentPath = <OpenTelemetry Java Agent location>
|
||||
// Specify the path for Google Cloud Authentication Extension for the Java Agent.
|
||||
def extensionPath = <Google Cloud Authentication Extension location>
|
||||
def googleCloudProjectId = <Your Google Cloud Project ID>
|
||||
def googleOtlpEndpoint = <Google Cloud OTLP endpoint>
|
||||
|
||||
val autoconf_config = listOf(
|
||||
"-javaagent:${otelAgentPath}",
|
||||
"-Dotel.javaagent.extensions=${extensionPath}",
|
||||
// Configure the GCP Auth extension using system properties.
|
||||
// This can also be configured using environment variables.
|
||||
"-Dgoogle.cloud.project=${googleCloudProjectId}",
|
||||
// Configure auto instrumentation.
|
||||
"-Dotel.exporter.otlp.traces.endpoint=${googleOtlpEndpoint}",
|
||||
'-Dotel.java.global-autoconfigure.enabled=true',
|
||||
// Optionally enable the built-in GCP resource detector
|
||||
'-Dotel.resource.providers.gcp.enabled=true'
|
||||
'-Dotel.traces.exporter=otlp',
|
||||
'-Dotel.metrics.exporter=logging'
|
||||
)
|
||||
|
||||
application {
|
||||
...
|
||||
applicationDefaultJvmArgs = autoconf_config
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Without OpenTelemetry Java agent
|
||||
|
||||
This extension can be used without the OpenTelemetry Java agent by leveraging the [OpenTelemetry SDK Autoconfigure](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md) module.\
|
||||
When using the autoconfigured SDK, simply adding this extension as a dependency automatically configures authentication headers and resource attributes for spans, enabling export to Google Cloud.
|
||||
|
||||
Below is a snippet showing how to use this extension as a dependency when the application is not instrumented using the OpenTelemetry Java agent.
|
||||
|
||||
```gradle
|
||||
dependencies {
|
||||
implementation("io.opentelemetry:opentelemetry-api")
|
||||
implementation("io.opentelemetry:opentelemetry-sdk")
|
||||
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
|
||||
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
|
||||
// include the auth extension dependency
|
||||
implementation("io.opentelemetry.contrib:opentelemetry-gcp-auth-extension")
|
||||
|
||||
// other dependencies
|
||||
...
|
||||
|
||||
}
|
||||
|
||||
val autoconf_config = listOf(
|
||||
'-Dgoogle.cloud.project=your-gcp-project-id',
|
||||
'-Dotel.exporter.otlp.endpoint=https://your.otlp.endpoint:1234',
|
||||
'-Dotel.traces.exporter=otlp',
|
||||
'-Dotel.java.global-autoconfigure.enabled=true'
|
||||
|
||||
// any additional args
|
||||
...
|
||||
)
|
||||
|
||||
application {
|
||||
applicationDefaultJvmArgs = autoconf_config
|
||||
|
||||
// additional configuration
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Component Owners
|
||||
|
||||
- [Josh Suereth](https://github.com/jsuereth), Google
|
||||
- [Pranav Sharma](https://github.com/psx95), Google
|
||||
|
||||
Learn more about component owners in [component_owners.yml](../.github/component_owners.yml).
|
|
@ -0,0 +1,114 @@
|
|||
plugins {
|
||||
id("otel.java-conventions")
|
||||
id("otel.publish-conventions")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
id("org.springframework.boot") version "2.7.18"
|
||||
}
|
||||
|
||||
description = "OpenTelemetry extension that provides GCP authentication support for OTLP exporters"
|
||||
otelJava.moduleName.set("io.opentelemetry.contrib.gcp.auth")
|
||||
|
||||
val agent: Configuration by configurations.creating {
|
||||
isCanBeResolved = true
|
||||
isCanBeConsumed = false
|
||||
}
|
||||
|
||||
dependencies {
|
||||
annotationProcessor("com.google.auto.service:auto-service")
|
||||
// We use `compileOnly` dependency because during runtime all necessary classes are provided by
|
||||
// javaagent itself.
|
||||
compileOnly("com.google.auto.service:auto-service-annotations")
|
||||
compileOnly("io.opentelemetry:opentelemetry-api")
|
||||
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
|
||||
compileOnly("io.opentelemetry:opentelemetry-exporter-otlp")
|
||||
|
||||
// Only dependencies added to `implementation` configuration will be picked up by Shadow plugin
|
||||
implementation("com.google.auth:google-auth-library-oauth2-http:1.30.1")
|
||||
|
||||
// Test dependencies
|
||||
testCompileOnly("com.google.auto.service:auto-service-annotations")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api")
|
||||
|
||||
testImplementation("io.opentelemetry:opentelemetry-api")
|
||||
testImplementation("io.opentelemetry:opentelemetry-exporter-otlp")
|
||||
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
|
||||
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
|
||||
testImplementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations")
|
||||
|
||||
testImplementation("org.awaitility:awaitility")
|
||||
testImplementation("org.mockito:mockito-inline")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter")
|
||||
testImplementation("org.mock-server:mockserver-netty:5.15.0")
|
||||
testImplementation("io.opentelemetry.proto:opentelemetry-proto:1.4.0-alpha")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-web:2.7.18")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter:2.7.18")
|
||||
testImplementation("org.springframework.boot:spring-boot-starter-test:2.7.18")
|
||||
|
||||
agent("io.opentelemetry.javaagent:opentelemetry-javaagent")
|
||||
}
|
||||
|
||||
tasks {
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
// exclude integration test
|
||||
exclude("io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.class")
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
archiveClassifier.set("")
|
||||
}
|
||||
|
||||
jar {
|
||||
// Disable standard jar
|
||||
enabled = false
|
||||
}
|
||||
|
||||
assemble {
|
||||
dependsOn(shadowJar)
|
||||
}
|
||||
|
||||
bootJar {
|
||||
// disable bootJar in build since it only runs as part of test
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
val builtLibsDir = layout.buildDirectory.dir("libs").get().asFile.absolutePath
|
||||
val javaAgentJarPath = "$builtLibsDir/otel-agent.jar"
|
||||
val authExtensionJarPath = "${tasks.shadowJar.get().archiveFile.get()}"
|
||||
|
||||
tasks.register<Copy>("copyAgent") {
|
||||
into(layout.buildDirectory.dir("libs"))
|
||||
from(configurations.named("agent") {
|
||||
rename("opentelemetry-javaagent(.*).jar", "otel-agent.jar")
|
||||
})
|
||||
}
|
||||
|
||||
tasks.register<Test>("IntegrationTest") {
|
||||
dependsOn(tasks.shadowJar)
|
||||
dependsOn(tasks.named("copyAgent"))
|
||||
|
||||
useJUnitPlatform()
|
||||
// include only the integration test file
|
||||
include("io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.class")
|
||||
|
||||
val fakeCredsFilePath = project.file("src/test/resources/fakecreds.json").absolutePath
|
||||
|
||||
environment("GOOGLE_CLOUD_QUOTA_PROJECT", "quota-project-id")
|
||||
environment("GOOGLE_APPLICATION_CREDENTIALS", fakeCredsFilePath)
|
||||
jvmArgs = listOf(
|
||||
"-javaagent:$javaAgentJarPath",
|
||||
"-Dotel.javaagent.extensions=$authExtensionJarPath",
|
||||
"-Dgoogle.cloud.project=my-gcp-project",
|
||||
"-Dotel.java.global-autoconfigure.enabled=true",
|
||||
"-Dotel.exporter.otlp.endpoint=http://localhost:4318",
|
||||
"-Dotel.resource.providers.gcp.enabled=true",
|
||||
"-Dotel.traces.exporter=otlp",
|
||||
"-Dotel.bsp.schedule.delay=2000",
|
||||
"-Dotel.metrics.exporter=none",
|
||||
"-Dotel.logs.exporter=none",
|
||||
"-Dotel.exporter.otlp.protocol=http/protobuf",
|
||||
"-Dmockserver.logLevel=off"
|
||||
)
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
# TODO: uncomment when ready to mark as stable
|
||||
# otel.stable=true
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.contrib.gcp.auth;
|
||||
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* An enum representing configurable options for a GCP Authentication Extension. Each option has a
|
||||
* user-readable name and can be configured using environment variables or system properties.
|
||||
*/
|
||||
public enum ConfigurableOption {
|
||||
/**
|
||||
* Represents the Google Cloud Project ID option. Can be configured using the environment variable
|
||||
* `GOOGLE_CLOUD_PROJECT` or the system property `google.cloud.project`.
|
||||
*/
|
||||
GOOGLE_CLOUD_PROJECT("Google Cloud Project ID");
|
||||
|
||||
private final String userReadableName;
|
||||
private final String environmentVariableName;
|
||||
private final String systemPropertyName;
|
||||
|
||||
ConfigurableOption(String userReadableName) {
|
||||
this.userReadableName = userReadableName;
|
||||
this.environmentVariableName = this.name();
|
||||
this.systemPropertyName =
|
||||
this.environmentVariableName.toLowerCase(Locale.ENGLISH).replace('_', '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the environment variable name associated with this option.
|
||||
*
|
||||
* @return the environment variable name (e.g., GOOGLE_CLOUD_PROJECT)
|
||||
*/
|
||||
String getEnvironmentVariable() {
|
||||
return this.environmentVariableName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the system property name associated with this option.
|
||||
*
|
||||
* @return the system property name (e.g., google.cloud.project)
|
||||
*/
|
||||
String getSystemProperty() {
|
||||
return this.systemPropertyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the configured value for this option. This method checks the environment variable
|
||||
* first and then the system property.
|
||||
*
|
||||
* @return The configured value as a string, or throws an exception if not configured.
|
||||
* @throws ConfigurationException if neither the environment variable nor the system property is
|
||||
* set.
|
||||
*/
|
||||
String getConfiguredValue() {
|
||||
String envVar = System.getenv(this.getEnvironmentVariable());
|
||||
String sysProp = System.getProperty(this.getSystemProperty());
|
||||
|
||||
if (envVar != null && !envVar.isEmpty()) {
|
||||
return envVar;
|
||||
} else if (sysProp != null && !sysProp.isEmpty()) {
|
||||
return sysProp;
|
||||
} else {
|
||||
throw new ConfigurationException(
|
||||
String.format(
|
||||
"GCP Authentication Extension not configured properly: %s not configured. Configure it by exporting environment variable %s or system property %s",
|
||||
this.userReadableName, this.getEnvironmentVariable(), this.getSystemProperty()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the value for this option, prioritizing environment variables and system properties.
|
||||
* If neither an environment variable nor a system property is set for this option, the provided
|
||||
* fallback function is used to determine the value.
|
||||
*
|
||||
* @param fallback A {@link Supplier} that provides the default value for the option when it is
|
||||
* not explicitly configured via an environment variable or system property.
|
||||
* @return The configured value for the option, obtained from the environment variable, system
|
||||
* property, or the fallback function, in that order of precedence.
|
||||
*/
|
||||
String getConfiguredValueWithFallback(Supplier<String> fallback) {
|
||||
try {
|
||||
return this.getConfiguredValue();
|
||||
} catch (ConfigurationException e) {
|
||||
return fallback.get();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* 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.trace.OtlpHttpSpanExporter;
|
||||
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder;
|
||||
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.resources.Resource;
|
||||
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
static final String QUOTA_USER_PROJECT_HEADER = "X-Goog-User-Project";
|
||||
static final String GCP_USER_PROJECT_ID_KEY = "gcp.project_id";
|
||||
|
||||
/**
|
||||
* Customizes the provided {@link AutoConfigurationCustomizer}.
|
||||
*
|
||||
* <p>This method attempts to retrieve Google Application Default Credentials (ADC) and performs
|
||||
* the following: - Adds authorization headers to the configured {@link SpanExporter} based on the
|
||||
* retrieved credentials. - Adds default properties for OTLP endpoint and resource attributes for
|
||||
* 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(AutoConfigurationCustomizer autoConfiguration) {
|
||||
try {
|
||||
GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
|
||||
autoConfiguration
|
||||
.addSpanExporterCustomizer(
|
||||
(exporter, configProperties) -> addAuthorizationHeaders(exporter, credentials))
|
||||
.addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource);
|
||||
} catch (IOException e) {
|
||||
throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int order() {
|
||||
return Integer.MAX_VALUE - 1;
|
||||
}
|
||||
|
||||
// Adds authorization headers to the calls made by the OtlpGrpcSpanExporter and
|
||||
// OtlpHttpSpanExporter.
|
||||
private static SpanExporter addAuthorizationHeaders(
|
||||
SpanExporter exporter, GoogleCredentials credentials) {
|
||||
if (exporter instanceof OtlpHttpSpanExporter) {
|
||||
OtlpHttpSpanExporterBuilder builder =
|
||||
((OtlpHttpSpanExporter) exporter)
|
||||
.toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials));
|
||||
return builder.build();
|
||||
} else if (exporter instanceof OtlpGrpcSpanExporter) {
|
||||
OtlpGrpcSpanExporterBuilder builder =
|
||||
((OtlpGrpcSpanExporter) exporter)
|
||||
.toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials));
|
||||
return builder.build();
|
||||
}
|
||||
return exporter;
|
||||
}
|
||||
|
||||
private static Map<String, String> getRequiredHeaderMap(GoogleCredentials credentials) {
|
||||
Map<String, String> gcpHeaders = new HashMap<>();
|
||||
try {
|
||||
credentials.refreshIfExpired();
|
||||
} catch (IOException e) {
|
||||
throw new GoogleAuthException(Reason.FAILED_ADC_REFRESH, e);
|
||||
}
|
||||
gcpHeaders.put(QUOTA_USER_PROJECT_HEADER, credentials.getQuotaProjectId());
|
||||
gcpHeaders.put("Authorization", "Bearer " + credentials.getAccessToken().getTokenValue());
|
||||
return gcpHeaders;
|
||||
}
|
||||
|
||||
// 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.getConfiguredValueWithFallback(
|
||||
() -> {
|
||||
try {
|
||||
GoogleCredentials googleCredentials = GoogleCredentials.getApplicationDefault();
|
||||
return googleCredentials.getQuotaProjectId();
|
||||
} catch (IOException e) {
|
||||
throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e);
|
||||
}
|
||||
});
|
||||
|
||||
Resource res =
|
||||
Resource.create(
|
||||
Attributes.of(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), gcpProjectId));
|
||||
return resource.merge(res);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.contrib.gcp.auth;
|
||||
|
||||
/**
|
||||
* An unchecked exception indicating a failure during Google authentication. This exception is
|
||||
* thrown when there are issues with retrieving or refreshing Google Application Default Credentials
|
||||
* (ADC).
|
||||
*/
|
||||
public class GoogleAuthException extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 149908685226796448L;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code GoogleAuthException} with the specified reason and cause.
|
||||
*
|
||||
* @param reason the reason for the authentication failure.
|
||||
* @param cause the underlying cause of the exception (e.g., an IOException).
|
||||
*/
|
||||
GoogleAuthException(Reason reason, Throwable cause) {
|
||||
super(reason.message, cause);
|
||||
}
|
||||
|
||||
/** Enumerates the possible reasons for a Google authentication failure. */
|
||||
enum Reason {
|
||||
/** Indicates a failure to retrieve Google Application Default Credentials. */
|
||||
FAILED_ADC_RETRIEVAL("Unable to retrieve Google Application Default Credentials."),
|
||||
/** Indicates a failure to retrieve Google Application Default Credentials. */
|
||||
FAILED_ADC_REFRESH("Unable to refresh Google Application Default Credentials.");
|
||||
|
||||
private final String message;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Reason} with the specified message.
|
||||
*
|
||||
* @param message the message describing the reason.
|
||||
*/
|
||||
Reason(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the message associated with this reason.
|
||||
*
|
||||
* @return the message describing the reason.
|
||||
*/
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.contrib.gcp.auth;
|
||||
|
||||
import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.GCP_USER_PROJECT_ID_KEY;
|
||||
import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.QUOTA_USER_PROJECT_HEADER;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.google.auth.oauth2.AccessToken;
|
||||
import com.google.auth.oauth2.GoogleCredentials;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import io.opentelemetry.api.common.AttributeKey;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
|
||||
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder;
|
||||
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
|
||||
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder;
|
||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
|
||||
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder;
|
||||
import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
|
||||
import io.opentelemetry.sdk.autoconfigure.internal.ComponentLoader;
|
||||
import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider;
|
||||
import io.opentelemetry.sdk.common.CompletableResultCode;
|
||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Supplier;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Captor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class GcpAuthAutoConfigurationCustomizerProviderTest {
|
||||
|
||||
@Mock private GoogleCredentials mockedGoogleCredentials;
|
||||
|
||||
@Captor private ArgumentCaptor<Supplier<Map<String, String>>> headerSupplierCaptor;
|
||||
|
||||
private static final ImmutableMap<String, String> otelProperties =
|
||||
ImmutableMap.of(
|
||||
"otel.traces.exporter",
|
||||
"otlp",
|
||||
"otel.metrics.exporter",
|
||||
"none",
|
||||
"otel.logs.exporter",
|
||||
"none",
|
||||
"otel.resource.attributes",
|
||||
"foo=bar");
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("CannotMockMethod")
|
||||
public void setup() {
|
||||
MockitoAnnotations.openMocks(this);
|
||||
Mockito.when(mockedGoogleCredentials.getQuotaProjectId()).thenReturn("test-project");
|
||||
Mockito.when(mockedGoogleCredentials.getAccessToken())
|
||||
.thenReturn(new AccessToken("fake", Date.from(Instant.now())));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomizerOtlpHttp() {
|
||||
OtlpHttpSpanExporter mockOtlpHttpSpanExporter = Mockito.mock(OtlpHttpSpanExporter.class);
|
||||
OtlpHttpSpanExporterBuilder otlpSpanExporterBuilder = OtlpHttpSpanExporter.builder();
|
||||
OtlpHttpSpanExporterBuilder spyOtlpHttpSpanExporterBuilder =
|
||||
Mockito.spy(otlpSpanExporterBuilder);
|
||||
Mockito.when(spyOtlpHttpSpanExporterBuilder.build()).thenReturn(mockOtlpHttpSpanExporter);
|
||||
|
||||
Mockito.when(mockOtlpHttpSpanExporter.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||
List<SpanData> exportedSpans = new ArrayList<>();
|
||||
Mockito.when(mockOtlpHttpSpanExporter.export(Mockito.anyCollection()))
|
||||
.thenAnswer(
|
||||
invocationOnMock -> {
|
||||
exportedSpans.addAll(invocationOnMock.getArgument(0));
|
||||
return CompletableResultCode.ofSuccess();
|
||||
});
|
||||
Mockito.when(mockOtlpHttpSpanExporter.toBuilder()).thenReturn(spyOtlpHttpSpanExporterBuilder);
|
||||
|
||||
try (MockedStatic<GoogleCredentials> googleCredentialsMockedStatic =
|
||||
Mockito.mockStatic(GoogleCredentials.class)) {
|
||||
googleCredentialsMockedStatic
|
||||
.when(GoogleCredentials::getApplicationDefault)
|
||||
.thenReturn(mockedGoogleCredentials);
|
||||
|
||||
OpenTelemetrySdk sdk = buildOpenTelemetrySdkWithExporter(mockOtlpHttpSpanExporter);
|
||||
generateTestSpan(sdk);
|
||||
CompletableResultCode code = sdk.shutdown();
|
||||
CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS);
|
||||
assertTrue(joinResult.isSuccess());
|
||||
|
||||
Mockito.verify(mockOtlpHttpSpanExporter, Mockito.times(1)).toBuilder();
|
||||
Mockito.verify(spyOtlpHttpSpanExporterBuilder, Mockito.times(1))
|
||||
.setHeaders(headerSupplierCaptor.capture());
|
||||
assertEquals(2, headerSupplierCaptor.getValue().get().size());
|
||||
assertThat(verifyAuthHeaders(headerSupplierCaptor.getValue().get())).isTrue();
|
||||
|
||||
Mockito.verify(mockOtlpHttpSpanExporter, Mockito.atLeast(1)).export(Mockito.anyCollection());
|
||||
|
||||
assertThat(exportedSpans)
|
||||
.hasSizeGreaterThan(0)
|
||||
.allSatisfy(
|
||||
spanData -> {
|
||||
assertThat(spanData.getResource().getAttributes().asMap())
|
||||
.containsEntry(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), "test-project")
|
||||
.containsEntry(AttributeKey.stringKey("foo"), "bar");
|
||||
assertThat(spanData.getAttributes().asMap())
|
||||
.containsKey(AttributeKey.longKey("work_loop"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomizerOtlpGrpc() {
|
||||
OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class);
|
||||
OtlpGrpcSpanExporterBuilder otlpSpanExporterBuilder = OtlpGrpcSpanExporter.builder();
|
||||
OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder =
|
||||
Mockito.spy(otlpSpanExporterBuilder);
|
||||
Mockito.when(spyOtlpGrpcSpanExporterBuilder.build()).thenReturn(mockOtlpGrpcSpanExporter);
|
||||
Mockito.when(mockOtlpGrpcSpanExporter.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
|
||||
List<SpanData> exportedSpans = new ArrayList<>();
|
||||
Mockito.when(mockOtlpGrpcSpanExporter.export(Mockito.anyCollection()))
|
||||
.thenAnswer(
|
||||
invocationOnMock -> {
|
||||
exportedSpans.addAll(invocationOnMock.getArgument(0));
|
||||
return CompletableResultCode.ofSuccess();
|
||||
});
|
||||
Mockito.when(mockOtlpGrpcSpanExporter.toBuilder()).thenReturn(spyOtlpGrpcSpanExporterBuilder);
|
||||
|
||||
try (MockedStatic<GoogleCredentials> googleCredentialsMockedStatic =
|
||||
Mockito.mockStatic(GoogleCredentials.class)) {
|
||||
googleCredentialsMockedStatic
|
||||
.when(GoogleCredentials::getApplicationDefault)
|
||||
.thenReturn(mockedGoogleCredentials);
|
||||
|
||||
OpenTelemetrySdk sdk = buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter);
|
||||
generateTestSpan(sdk);
|
||||
CompletableResultCode code = sdk.shutdown();
|
||||
CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS);
|
||||
assertTrue(joinResult.isSuccess());
|
||||
|
||||
Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.times(1)).toBuilder();
|
||||
Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1))
|
||||
.setHeaders(headerSupplierCaptor.capture());
|
||||
assertEquals(2, headerSupplierCaptor.getValue().get().size());
|
||||
verifyAuthHeaders(headerSupplierCaptor.getValue().get());
|
||||
|
||||
Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.atLeast(1)).export(Mockito.anyCollection());
|
||||
|
||||
assertThat(exportedSpans)
|
||||
.hasSizeGreaterThan(0)
|
||||
.allSatisfy(
|
||||
spanData -> {
|
||||
assertThat(spanData.getResource().getAttributes().asMap())
|
||||
.containsEntry(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), "test-project")
|
||||
.containsEntry(AttributeKey.stringKey("foo"), "bar");
|
||||
assertThat(spanData.getAttributes().asMap())
|
||||
.containsKey(AttributeKey.longKey("work_loop"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(SpanExporter spanExporter) {
|
||||
SpiHelper spiHelper =
|
||||
SpiHelper.create(GcpAuthAutoConfigurationCustomizerProviderTest.class.getClassLoader());
|
||||
AutoConfiguredOpenTelemetrySdkBuilder builder =
|
||||
AutoConfiguredOpenTelemetrySdk.builder().addPropertiesSupplier(() -> otelProperties);
|
||||
AutoConfigureUtil.setComponentLoader(
|
||||
builder,
|
||||
new ComponentLoader() {
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> List<T> load(Class<T> spiClass) {
|
||||
if (spiClass == ConfigurableSpanExporterProvider.class) {
|
||||
return Collections.singletonList(
|
||||
(T)
|
||||
new ConfigurableSpanExporterProvider() {
|
||||
@Override
|
||||
public SpanExporter createExporter(ConfigProperties configProperties) {
|
||||
return spanExporter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "otlp";
|
||||
}
|
||||
});
|
||||
}
|
||||
return spiHelper.load(spiClass);
|
||||
}
|
||||
});
|
||||
return builder.build().getOpenTelemetrySdk();
|
||||
}
|
||||
|
||||
private static boolean verifyAuthHeaders(Map<String, String> headers) {
|
||||
Set<Entry<String, String>> headerEntrySet = headers.entrySet();
|
||||
return headerEntrySet.contains(new SimpleEntry<>(QUOTA_USER_PROJECT_HEADER, "test-project"))
|
||||
&& headerEntrySet.contains(new SimpleEntry<>("Authorization", "Bearer fake"));
|
||||
}
|
||||
|
||||
private static void generateTestSpan(OpenTelemetrySdk openTelemetrySdk) {
|
||||
Span span = openTelemetrySdk.getTracer("test").spanBuilder("sample").startSpan();
|
||||
try (Scope ignored = span.makeCurrent()) {
|
||||
long workOutput = busyloop();
|
||||
span.setAttribute("work_loop", workOutput);
|
||||
} finally {
|
||||
span.end();
|
||||
}
|
||||
}
|
||||
|
||||
// loop to simulate work done
|
||||
private static long busyloop() {
|
||||
Instant start = Instant.now();
|
||||
Instant end;
|
||||
long counter = 0;
|
||||
do {
|
||||
counter++;
|
||||
end = Instant.now();
|
||||
} while (Duration.between(start, end).toMillis() < 1000);
|
||||
return counter;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.contrib.gcp.auth;
|
||||
|
||||
import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.GCP_USER_PROJECT_ID_KEY;
|
||||
import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.QUOTA_USER_PROJECT_HEADER;
|
||||
import static org.awaitility.Awaitility.await;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockserver.model.HttpRequest.request;
|
||||
import static org.mockserver.model.HttpResponse.response;
|
||||
import static org.mockserver.stop.Stop.stopQuietly;
|
||||
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
import io.opentelemetry.contrib.gcp.auth.springapp.Application;
|
||||
import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest;
|
||||
import io.opentelemetry.proto.common.v1.AnyValue;
|
||||
import io.opentelemetry.proto.common.v1.KeyValue;
|
||||
import io.opentelemetry.proto.trace.v1.ResourceSpans;
|
||||
import java.net.URI;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockserver.client.MockServerClient;
|
||||
import org.mockserver.integration.ClientAndServer;
|
||||
import org.mockserver.model.Body;
|
||||
import org.mockserver.model.Headers;
|
||||
import org.mockserver.model.HttpRequest;
|
||||
import org.mockserver.model.JsonBody;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
||||
import org.springframework.boot.test.web.server.LocalServerPort;
|
||||
|
||||
@SpringBootTest(
|
||||
classes = {Application.class},
|
||||
webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class GcpAuthExtensionEndToEndTest {
|
||||
|
||||
@LocalServerPort private int testApplicationPort; // port at which the spring app is running
|
||||
|
||||
@Autowired private TestRestTemplate template;
|
||||
|
||||
// The port at which the backend server will receive telemetry
|
||||
private static final int EXPORTER_ENDPOINT_PORT = 4318;
|
||||
// The port at which the mock GCP OAuth 2.0 server will run
|
||||
private static final int MOCK_GCP_OAUTH2_PORT = 8090;
|
||||
|
||||
// Backend server to which the application under test will export traces
|
||||
// the export config is specified in the build.gradle file.
|
||||
private static ClientAndServer backendServer;
|
||||
|
||||
// Mock server to intercept calls to the GCP OAuth 2.0 server and provide fake credentials
|
||||
private static ClientAndServer mockGcpOAuth2Server;
|
||||
|
||||
private static final String DUMMY_GCP_QUOTA_PROJECT = System.getenv("GOOGLE_CLOUD_QUOTA_PROJECT");
|
||||
private static final String DUMMY_GCP_PROJECT = System.getProperty("google.cloud.project");
|
||||
|
||||
@BeforeAll
|
||||
public static void setup() throws NoSuchAlgorithmException, KeyManagementException {
|
||||
// Setup proxy host(s)
|
||||
System.setProperty("http.proxyHost", "localhost");
|
||||
System.setProperty("http.proxyPort", MOCK_GCP_OAUTH2_PORT + "");
|
||||
System.setProperty("https.proxyHost", "localhost");
|
||||
System.setProperty("https.proxyPort", MOCK_GCP_OAUTH2_PORT + "");
|
||||
System.setProperty("http.nonProxyHost", "localhost");
|
||||
System.setProperty("https.nonProxyHost", "localhost");
|
||||
|
||||
// Disable SSL validation for integration test
|
||||
// The OAuth2 token validation requires SSL validation
|
||||
disableSSLValidation();
|
||||
|
||||
// Set up mock OTLP backend server to which traces will be exported
|
||||
backendServer = ClientAndServer.startClientAndServer(EXPORTER_ENDPOINT_PORT);
|
||||
backendServer.when(request()).respond(response().withStatusCode(200));
|
||||
|
||||
// Set up the mock gcp metadata server to provide fake credentials
|
||||
String accessTokenResponse =
|
||||
"{\"access_token\": \"fake.access_token\",\"expires_in\": 3600, \"token_type\": \"Bearer\"}";
|
||||
mockGcpOAuth2Server = ClientAndServer.startClientAndServer(MOCK_GCP_OAUTH2_PORT);
|
||||
|
||||
MockServerClient mockServerClient =
|
||||
new MockServerClient("localhost", MOCK_GCP_OAUTH2_PORT).withSecure(true);
|
||||
|
||||
// mock the token refresh - always respond with 200
|
||||
mockServerClient
|
||||
.when(request().withMethod("POST").withPath("/token"))
|
||||
.respond(
|
||||
response()
|
||||
.withStatusCode(200)
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withBody(new JsonBody(accessTokenResponse)));
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void teardown() {
|
||||
// Stop the backend server
|
||||
stopQuietly(backendServer);
|
||||
stopQuietly(mockGcpOAuth2Server);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authExtensionSmokeTest() {
|
||||
template.getForEntity(
|
||||
URI.create("http://localhost:" + testApplicationPort + "/ping"), String.class);
|
||||
|
||||
await()
|
||||
.atMost(Duration.ofSeconds(10))
|
||||
.untilAsserted(
|
||||
() -> {
|
||||
HttpRequest[] requests = backendServer.retrieveRecordedRequests(request());
|
||||
List<Headers> extractedHeaders = extractHeadersFromRequests(requests);
|
||||
verifyRequestHeaders(extractedHeaders);
|
||||
|
||||
List<ResourceSpans> extractedResourceSpans =
|
||||
extractResourceSpansFromRequests(requests);
|
||||
verifyResourceAttributes(extractedResourceSpans);
|
||||
});
|
||||
}
|
||||
|
||||
// Helper methods
|
||||
|
||||
private static void disableSSLValidation()
|
||||
throws NoSuchAlgorithmException, KeyManagementException {
|
||||
TrustManager[] trustAllCerts =
|
||||
new TrustManager[] {
|
||||
new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(X509Certificate[] chain, String authType) {}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(X509Certificate[] chain, String authType) {}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
SSLContext sc = SSLContext.getInstance("SSL");
|
||||
sc.init(null, trustAllCerts, new java.security.SecureRandom());
|
||||
HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
|
||||
}
|
||||
|
||||
private static void verifyResourceAttributes(List<ResourceSpans> extractedResourceSpans) {
|
||||
extractedResourceSpans.forEach(
|
||||
resourceSpan ->
|
||||
assertTrue(
|
||||
resourceSpan
|
||||
.getResource()
|
||||
.getAttributesList()
|
||||
.contains(
|
||||
KeyValue.newBuilder()
|
||||
.setKey(GCP_USER_PROJECT_ID_KEY)
|
||||
.setValue(AnyValue.newBuilder().setStringValue(DUMMY_GCP_PROJECT))
|
||||
.build())));
|
||||
}
|
||||
|
||||
private static void verifyRequestHeaders(List<Headers> extractedHeaders) {
|
||||
assertFalse(extractedHeaders.isEmpty());
|
||||
// verify if extension added the required headers
|
||||
extractedHeaders.forEach(
|
||||
headers -> {
|
||||
assertTrue(headers.containsEntry(QUOTA_USER_PROJECT_HEADER, DUMMY_GCP_QUOTA_PROJECT));
|
||||
assertTrue(headers.containsEntry("Authorization", "Bearer fake.access_token"));
|
||||
});
|
||||
}
|
||||
|
||||
private static List<Headers> extractHeadersFromRequests(HttpRequest[] requests) {
|
||||
return Arrays.stream(requests).map(HttpRequest::getHeaders).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract resource spans from http requests received by a telemetry collector.
|
||||
*
|
||||
* @param requests Request received by a http server trace collector
|
||||
* @return spans extracted from the request body
|
||||
*/
|
||||
private static List<ResourceSpans> extractResourceSpansFromRequests(HttpRequest[] requests) {
|
||||
return Arrays.stream(requests)
|
||||
.map(HttpRequest::getBody)
|
||||
.map(GcpAuthExtensionEndToEndTest::getExportTraceServiceRequest)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.flatMap(
|
||||
exportTraceServiceRequest -> exportTraceServiceRequest.getResourceSpansList().stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private static Optional<ExportTraceServiceRequest> getExportTraceServiceRequest(Body<?> body) {
|
||||
try {
|
||||
return Optional.ofNullable(ExportTraceServiceRequest.parseFrom(body.getRawBytes()));
|
||||
} catch (InvalidProtocolBufferException e) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.contrib.gcp.auth.springapp;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
@SuppressWarnings("PrivateConstructorForUtilityClass")
|
||||
public class Application {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.contrib.gcp.auth.springapp;
|
||||
|
||||
import io.opentelemetry.instrumentation.annotations.WithSpan;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Random;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class Controller {
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
@GetMapping("/ping")
|
||||
public String ping() {
|
||||
int busyTime = random.nextInt(200);
|
||||
busyloop(busyTime);
|
||||
return "pong";
|
||||
}
|
||||
|
||||
@WithSpan
|
||||
private static long busyloop(int busyMillis) {
|
||||
Instant start = Instant.now();
|
||||
Instant end;
|
||||
long counter = 0;
|
||||
do {
|
||||
counter++;
|
||||
end = Instant.now();
|
||||
} while (Duration.between(start, end).toMillis() < busyMillis);
|
||||
return counter;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"type": "service_account",
|
||||
"project_id": "quota-project-id",
|
||||
"private_key_id": "aljmafmlamlmmasma",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALX0PQoe1igW12ikv1bN/r9lN749y2ijmbc/mFHPyS3hNTyOCjDvBbXYbDhQJzWVUikh4mvGBA07qTj79Xc3yBDfKP2IeyYQIFe0t0zkd7R9Zdn98Y2rIQC47aAbDfubtkU1U72t4zL11kHvoa0/RuFZjncvlr42X7be7lYh4p3NAgMBAAECgYASk5wDw4Az2ZkmeuN6Fk/y9H+Lcb2pskJIXjrL533vrDWGOC48LrsThMQPv8cxBky8HFSEklPpkfTF95tpD43iVwJRB/GrCtGTw65IfJ4/tI09h6zGc4yqvIo1cHX/LQ+SxKLGyir/dQM925rGt/VojxY5ryJR7GLbCzxPnJm/oQJBANwOCO6D2hy1LQYJhXh7O+RLtA/tSnT1xyMQsGT+uUCMiKS2bSKx2wxo9k7h3OegNJIu1q6nZ6AbxDK8H3+d0dUCQQDTrPSXagBxzp8PecbaCHjzNRSQE2in81qYnrAFNB4o3DpHyMMY6s5ALLeHKscEWnqP8Ur6X4PvzZecCWU9BKAZAkAutLPknAuxSCsUOvUfS1i87ex77Ot+w6POp34pEX+UWb+u5iFn2cQacDTHLV1LtE80L8jVLSbrbrlH43H0DjU5AkEAgidhycxS86dxpEljnOMCw8CKoUBd5I880IUahEiUltk7OLJYS/Ts1wbn3kPOVX3wyJs8WBDtBkFrDHW2ezth2QJADj3e1YhMVdjJW5jqwlD/VNddGjgzyunmiZg0uOXsHXbytYmsA545S8KRQFaJKFXYYFo2kOjqOiC1T2cAzMDjCQ==\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "sample@appspot.gserviceaccount.com",
|
||||
"client_id": "100000000000000000221",
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
"auth_provider_x509_cert_url": "",
|
||||
"client_x509_cert_url": "",
|
||||
"universe_domain": "googleapis.com"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
mock-maker-inline
|
|
@ -62,3 +62,4 @@ include(":gcp-resources")
|
|||
include(":span-stacktrace")
|
||||
include(":inferred-spans")
|
||||
include(":opamp-client")
|
||||
include(":gcp-auth-extension")
|
||||
|
|
Loading…
Reference in New Issue