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 GCP authentication extension #1631

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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
124 changes: 124 additions & 0 deletions gcp-auth-extension/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Google Cloud Authentication Extension for OpenTelemetry Java Agent

The Google Cloud Auth Extension allows the users to export telemetry from their applications auto-instrumented using the OpenTelemetry Java Agent 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")
// use the shaded variant of the 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).
114 changes: 114 additions & 0 deletions gcp-auth-extension/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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 Java Agent Extension that enables 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"
)
}
2 changes: 2 additions & 0 deletions gcp-auth-extension/gradle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# TODO: uncomment when ready to mark as stable
# otel.stable=true
Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Loading
Loading