Skip to content

Commit 7480d43

Browse files
authored
Add GCP authentication extension (open-telemetry#1631)
1 parent deb9746 commit 7480d43

File tree

16 files changed

+1046
-0
lines changed

16 files changed

+1046
-0
lines changed

.github/component_owners.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ components:
3333
gcp-resources:
3434
- jsuereth
3535
- psx95
36+
gcp-auth-extension:
37+
- jsuereth
38+
- psx95
3639
jfr-connection:
3740
- breedx-splk
3841
- jeanbisutti

.github/scripts/draft-change-log-entries.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ component_names["compressors/"]="Compressors"
3232
component_names["consistent-sampling/"]="Consistent sampling"
3333
component_names["disk-buffering/"]="Disk buffering"
3434
component_names["gcp-resources/"]="GCP Resources"
35+
component_names["gcp-auth-extension/"]="GCP authentication extension"
3536
component_names["inferred-spans/"]="Inferred spans"
3637
component_names["jfr-connection/"]="JFR connection"
3738
component_names["jfr-events/"]="JFR events"

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ feature or via instrumentation, this project is hopefully for you.
1818
| alpha | [zstd Compressor](./compressors/compressor-zstd/README.md) |
1919
| alpha | [Consistent Sampling](./consistent-sampling/README.md) |
2020
| alpha | [Disk Buffering](./disk-buffering/README.md) |
21+
| alpha | [GCP Authentication Extension](./gcp-auth-extension/README.md) |
2122
| beta | [GCP Resources](./gcp-resources/README.md) |
2223
| beta | [Inferred Spans](./inferred-spans/README.md) |
2324
| alpha | [JFR Connection](./jfr-connection/README.md) |

gcp-auth-extension/README.md

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
# Google Cloud Authentication Extension
2+
3+
The Google Cloud Auth Extension allows the users to export telemetry from their applications to Google Cloud using the built-in OTLP exporters.\
4+
The extension takes care of the necessary configuration required to authenticate to GCP to successfully export telemetry.
5+
6+
## Prerequisites
7+
8+
### Ensure the presence of Google Cloud Credentials on your machine/environment
9+
10+
```shell
11+
gcloud auth application-default login
12+
```
13+
14+
Executing this command will save your application credentials to default path which will depend on the type of machine -
15+
16+
- Linux, macOS: `$HOME/.config/gcloud/application_default_credentials.json`
17+
- Windows: `%APPDATA%\gcloud\application_default_credentials.json`
18+
19+
**NOTE: This method of authentication is not recommended for production environments.**
20+
21+
Next, export the credentials to `GOOGLE_APPLICATION_CREDENTIALS` environment variable -
22+
23+
For Linux & MacOS:
24+
25+
```shell
26+
export GOOGLE_APPLICATION_CREDENTIALS=$HOME/.config/gcloud/application_default_credentials.json
27+
```
28+
29+
These credentials are built-in running in a Google App Engine, Google Cloud Shell or Google Compute Engine environment.
30+
31+
### Configuring the extension
32+
33+
The extension can be configured either by environment variables or system properties.
34+
35+
Here is a list of configurable options for the extension:
36+
37+
- `GOOGLE_CLOUD_PROJECT`: Environment variable that represents the Google Cloud Project ID to which the telemetry needs to be exported.
38+
- Can also be configured using `google.cloud.project` system property.
39+
- 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).
40+
41+
## Usage
42+
43+
### With OpenTelemetry Java agent
44+
45+
The OpenTelemetry Java Agent Extension can be easily added to any Java application by modifying the startup command to the application.
46+
For more information on Extensions, see the [documentation here](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/examples/extension/README.md).
47+
48+
Below is a snippet showing how to add the extension to a Java application using the Gradle build system.
49+
50+
```gradle
51+
// Specify OpenTelemetry Autoinstrumentation Java Agent Path.
52+
def otelAgentPath = <OpenTelemetry Java Agent location>
53+
// Specify the path for Google Cloud Authentication Extension for the Java Agent.
54+
def extensionPath = <Google Cloud Authentication Extension location>
55+
def googleCloudProjectId = <Your Google Cloud Project ID>
56+
def googleOtlpEndpoint = <Google Cloud OTLP endpoint>
57+
58+
val autoconf_config = listOf(
59+
"-javaagent:${otelAgentPath}",
60+
"-Dotel.javaagent.extensions=${extensionPath}",
61+
// Configure the GCP Auth extension using system properties.
62+
// This can also be configured using environment variables.
63+
"-Dgoogle.cloud.project=${googleCloudProjectId}",
64+
// Configure auto instrumentation.
65+
"-Dotel.exporter.otlp.traces.endpoint=${googleOtlpEndpoint}",
66+
'-Dotel.java.global-autoconfigure.enabled=true',
67+
// Optionally enable the built-in GCP resource detector
68+
'-Dotel.resource.providers.gcp.enabled=true'
69+
'-Dotel.traces.exporter=otlp',
70+
'-Dotel.metrics.exporter=logging'
71+
)
72+
73+
application {
74+
...
75+
applicationDefaultJvmArgs = autoconf_config
76+
...
77+
}
78+
```
79+
80+
### Without OpenTelemetry Java agent
81+
82+
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.\
83+
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.
84+
85+
Below is a snippet showing how to use this extension as a dependency when the application is not instrumented using the OpenTelemetry Java agent.
86+
87+
```gradle
88+
dependencies {
89+
implementation("io.opentelemetry:opentelemetry-api")
90+
implementation("io.opentelemetry:opentelemetry-sdk")
91+
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
92+
implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
93+
// include the auth extension dependency
94+
implementation("io.opentelemetry.contrib:opentelemetry-gcp-auth-extension")
95+
96+
// other dependencies
97+
...
98+
99+
}
100+
101+
val autoconf_config = listOf(
102+
'-Dgoogle.cloud.project=your-gcp-project-id',
103+
'-Dotel.exporter.otlp.endpoint=https://your.otlp.endpoint:1234',
104+
'-Dotel.traces.exporter=otlp',
105+
'-Dotel.java.global-autoconfigure.enabled=true'
106+
107+
// any additional args
108+
...
109+
)
110+
111+
application {
112+
applicationDefaultJvmArgs = autoconf_config
113+
114+
// additional configuration
115+
...
116+
}
117+
```
118+
119+
## Component Owners
120+
121+
- [Josh Suereth](https://github.com/jsuereth), Google
122+
- [Pranav Sharma](https://github.com/psx95), Google
123+
124+
Learn more about component owners in [component_owners.yml](../.github/component_owners.yml).
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
plugins {
2+
id("otel.java-conventions")
3+
id("otel.publish-conventions")
4+
id("com.github.johnrengelman.shadow")
5+
id("org.springframework.boot") version "2.7.18"
6+
}
7+
8+
description = "OpenTelemetry extension that provides GCP authentication support for OTLP exporters"
9+
otelJava.moduleName.set("io.opentelemetry.contrib.gcp.auth")
10+
11+
val agent: Configuration by configurations.creating {
12+
isCanBeResolved = true
13+
isCanBeConsumed = false
14+
}
15+
16+
dependencies {
17+
annotationProcessor("com.google.auto.service:auto-service")
18+
// We use `compileOnly` dependency because during runtime all necessary classes are provided by
19+
// javaagent itself.
20+
compileOnly("com.google.auto.service:auto-service-annotations")
21+
compileOnly("io.opentelemetry:opentelemetry-api")
22+
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
23+
compileOnly("io.opentelemetry:opentelemetry-exporter-otlp")
24+
25+
// Only dependencies added to `implementation` configuration will be picked up by Shadow plugin
26+
implementation("com.google.auth:google-auth-library-oauth2-http:1.30.1")
27+
28+
// Test dependencies
29+
testCompileOnly("com.google.auto.service:auto-service-annotations")
30+
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
31+
testImplementation("org.junit.jupiter:junit-jupiter-api")
32+
33+
testImplementation("io.opentelemetry:opentelemetry-api")
34+
testImplementation("io.opentelemetry:opentelemetry-exporter-otlp")
35+
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
36+
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
37+
testImplementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations")
38+
39+
testImplementation("org.awaitility:awaitility")
40+
testImplementation("org.mockito:mockito-inline")
41+
testImplementation("org.mockito:mockito-junit-jupiter")
42+
testImplementation("org.mock-server:mockserver-netty:5.15.0")
43+
testImplementation("io.opentelemetry.proto:opentelemetry-proto:1.4.0-alpha")
44+
testImplementation("org.springframework.boot:spring-boot-starter-web:2.7.18")
45+
testImplementation("org.springframework.boot:spring-boot-starter:2.7.18")
46+
testImplementation("org.springframework.boot:spring-boot-starter-test:2.7.18")
47+
48+
agent("io.opentelemetry.javaagent:opentelemetry-javaagent")
49+
}
50+
51+
tasks {
52+
test {
53+
useJUnitPlatform()
54+
// exclude integration test
55+
exclude("io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.class")
56+
}
57+
58+
shadowJar {
59+
archiveClassifier.set("")
60+
}
61+
62+
jar {
63+
// Disable standard jar
64+
enabled = false
65+
}
66+
67+
assemble {
68+
dependsOn(shadowJar)
69+
}
70+
71+
bootJar {
72+
// disable bootJar in build since it only runs as part of test
73+
enabled = false
74+
}
75+
}
76+
77+
val builtLibsDir = layout.buildDirectory.dir("libs").get().asFile.absolutePath
78+
val javaAgentJarPath = "$builtLibsDir/otel-agent.jar"
79+
val authExtensionJarPath = "${tasks.shadowJar.get().archiveFile.get()}"
80+
81+
tasks.register<Copy>("copyAgent") {
82+
into(layout.buildDirectory.dir("libs"))
83+
from(configurations.named("agent") {
84+
rename("opentelemetry-javaagent(.*).jar", "otel-agent.jar")
85+
})
86+
}
87+
88+
tasks.register<Test>("IntegrationTest") {
89+
dependsOn(tasks.shadowJar)
90+
dependsOn(tasks.named("copyAgent"))
91+
92+
useJUnitPlatform()
93+
// include only the integration test file
94+
include("io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.class")
95+
96+
val fakeCredsFilePath = project.file("src/test/resources/fakecreds.json").absolutePath
97+
98+
environment("GOOGLE_CLOUD_QUOTA_PROJECT", "quota-project-id")
99+
environment("GOOGLE_APPLICATION_CREDENTIALS", fakeCredsFilePath)
100+
jvmArgs = listOf(
101+
"-javaagent:$javaAgentJarPath",
102+
"-Dotel.javaagent.extensions=$authExtensionJarPath",
103+
"-Dgoogle.cloud.project=my-gcp-project",
104+
"-Dotel.java.global-autoconfigure.enabled=true",
105+
"-Dotel.exporter.otlp.endpoint=http://localhost:4318",
106+
"-Dotel.resource.providers.gcp.enabled=true",
107+
"-Dotel.traces.exporter=otlp",
108+
"-Dotel.bsp.schedule.delay=2000",
109+
"-Dotel.metrics.exporter=none",
110+
"-Dotel.logs.exporter=none",
111+
"-Dotel.exporter.otlp.protocol=http/protobuf",
112+
"-Dmockserver.logLevel=off"
113+
)
114+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# TODO: uncomment when ready to mark as stable
2+
# otel.stable=true
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.gcp.auth;
7+
8+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
9+
import java.util.Locale;
10+
import java.util.function.Supplier;
11+
12+
/**
13+
* An enum representing configurable options for a GCP Authentication Extension. Each option has a
14+
* user-readable name and can be configured using environment variables or system properties.
15+
*/
16+
public enum ConfigurableOption {
17+
/**
18+
* Represents the Google Cloud Project ID option. Can be configured using the environment variable
19+
* `GOOGLE_CLOUD_PROJECT` or the system property `google.cloud.project`.
20+
*/
21+
GOOGLE_CLOUD_PROJECT("Google Cloud Project ID");
22+
23+
private final String userReadableName;
24+
private final String environmentVariableName;
25+
private final String systemPropertyName;
26+
27+
ConfigurableOption(String userReadableName) {
28+
this.userReadableName = userReadableName;
29+
this.environmentVariableName = this.name();
30+
this.systemPropertyName =
31+
this.environmentVariableName.toLowerCase(Locale.ENGLISH).replace('_', '.');
32+
}
33+
34+
/**
35+
* Returns the environment variable name associated with this option.
36+
*
37+
* @return the environment variable name (e.g., GOOGLE_CLOUD_PROJECT)
38+
*/
39+
String getEnvironmentVariable() {
40+
return this.environmentVariableName;
41+
}
42+
43+
/**
44+
* Returns the system property name associated with this option.
45+
*
46+
* @return the system property name (e.g., google.cloud.project)
47+
*/
48+
String getSystemProperty() {
49+
return this.systemPropertyName;
50+
}
51+
52+
/**
53+
* Retrieves the configured value for this option. This method checks the environment variable
54+
* first and then the system property.
55+
*
56+
* @return The configured value as a string, or throws an exception if not configured.
57+
* @throws ConfigurationException if neither the environment variable nor the system property is
58+
* set.
59+
*/
60+
String getConfiguredValue() {
61+
String envVar = System.getenv(this.getEnvironmentVariable());
62+
String sysProp = System.getProperty(this.getSystemProperty());
63+
64+
if (envVar != null && !envVar.isEmpty()) {
65+
return envVar;
66+
} else if (sysProp != null && !sysProp.isEmpty()) {
67+
return sysProp;
68+
} else {
69+
throw new ConfigurationException(
70+
String.format(
71+
"GCP Authentication Extension not configured properly: %s not configured. Configure it by exporting environment variable %s or system property %s",
72+
this.userReadableName, this.getEnvironmentVariable(), this.getSystemProperty()));
73+
}
74+
}
75+
76+
/**
77+
* Retrieves the value for this option, prioritizing environment variables and system properties.
78+
* If neither an environment variable nor a system property is set for this option, the provided
79+
* fallback function is used to determine the value.
80+
*
81+
* @param fallback A {@link Supplier} that provides the default value for the option when it is
82+
* not explicitly configured via an environment variable or system property.
83+
* @return The configured value for the option, obtained from the environment variable, system
84+
* property, or the fallback function, in that order of precedence.
85+
*/
86+
String getConfiguredValueWithFallback(Supplier<String> fallback) {
87+
try {
88+
return this.getConfiguredValue();
89+
} catch (ConfigurationException e) {
90+
return fallback.get();
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)