Skip to content

Commit 7f0c1d3

Browse files
authored
feat: add metric headers (#1503)
context: b/339259830 and [go/send-auth-metrics-java](http://goto.google.com/send-auth-metrics-java) Changes include: - expose `Credentials` type via `getMetricsCredentialType()`. Override this method for UserCredentials, ServiceAccountCredentials, ImpersonatedCredentials, and ComputeEngineCredentials. This is used in both token request and token usage flows. - add metric headers for each of the in-scope token requests. Below are examples of each request flow with added metrics: - User credentials request (at/id): “gl-java/19.0.1 auth/1.24.3 cred-type/u” - SA credentials, VM credentials or Impersonated credentials requests (at/id): “gl-java/19.0.1 auth/1.24.3 auth-request-type/at cred-type/sa” - MDS ping (This is used in ADC during the credential detection): “gl-java/19.0.1 auth/1.24.3 auth-request-type/mds” - What is not tracked: ComputeEngineCredentials getUniverseDomain and getAccount does not send metrics header; TPC flows does not send metrics headers. Related pr: adding for cred_type for token usage requests googleapis/sdk-platform-java#3186
1 parent 6a2c62e commit 7f0c1d3

16 files changed

+422
-43
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Redistribution and use in source and binary forms, with or without
5+
* modification, are permitted provided that the following conditions are
6+
* met:
7+
*
8+
* * Redistributions of source code must retain the above copyright
9+
* notice, this list of conditions and the following disclaimer.
10+
* * Redistributions in binary form must reproduce the above
11+
* copyright notice, this list of conditions and the following disclaimer
12+
* in the documentation and/or other materials provided with the
13+
* distribution.
14+
*
15+
* * Neither the name of Google LLC nor the names of its
16+
* contributors may be used to endorse or promote products derived from
17+
* this software without specific prior written permission.
18+
*
19+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21+
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22+
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23+
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24+
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25+
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26+
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27+
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28+
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30+
*/
31+
32+
package com.google.auth;
33+
34+
/**
35+
* Defines the different types of credentials that can be used for metrics.
36+
*
37+
* <p>Each credential type is associated with a label that is used for reporting purposes. Add new
38+
* enum constant only when corresponding configs established.
39+
*
40+
* <p>Credentials with type {@code CredentialTypeForMetrics.DO_NOT_SEND} is default value for
41+
* credential implementations that do not set type specifically. It is not expected to send metrics.
42+
*
43+
* <p>
44+
*
45+
* @see #getLabel()
46+
*/
47+
public enum CredentialTypeForMetrics {
48+
USER_CREDENTIALS("u"),
49+
SERVICE_ACCOUNT_CREDENTIALS_AT("sa"),
50+
SERVICE_ACCOUNT_CREDENTIALS_JWT("jwt"),
51+
VM_CREDENTIALS("mds"),
52+
IMPERSONATED_CREDENTIALS("imp"),
53+
DO_NOT_SEND("dns");
54+
55+
private String label;
56+
57+
private CredentialTypeForMetrics(String label) {
58+
this.label = label;
59+
}
60+
61+
public String getLabel() {
62+
return label;
63+
}
64+
}

credentials/java/com/google/auth/Credentials.java

+12
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ public String getUniverseDomain() throws IOException {
7070
return GOOGLE_DEFAULT_UNIVERSE;
7171
}
7272

73+
/**
74+
* Gets the credential type used for internal metrics header.
75+
*
76+
* <p>The default is {@code CredentialTypeForMetrics.DO_NOT_SEND}. For a credential that is
77+
* established to track for metrics, this default should be overridden.
78+
*
79+
* @return a enum value for credential type
80+
*/
81+
public CredentialTypeForMetrics getMetricsCredentialType() {
82+
return CredentialTypeForMetrics.DO_NOT_SEND;
83+
}
84+
7385
/**
7486
* Get the current request metadata, refreshing tokens if required.
7587
*

oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java

+28-7
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,12 @@
4141
import com.google.api.client.http.HttpStatusCodes;
4242
import com.google.api.client.json.JsonObjectParser;
4343
import com.google.api.client.util.GenericData;
44+
import com.google.auth.CredentialTypeForMetrics;
4445
import com.google.auth.Credentials;
4546
import com.google.auth.Retryable;
4647
import com.google.auth.ServiceAccountSigner;
4748
import com.google.auth.http.HttpTransportFactory;
49+
import com.google.auth.oauth2.MetricsUtils.RequestType;
4850
import com.google.common.annotations.VisibleForTesting;
4951
import com.google.common.base.Joiner;
5052
import com.google.common.base.MoreObjects.ToStringHelper;
@@ -133,7 +135,6 @@ public class ComputeEngineCredentials extends GoogleCredentials
133135
*/
134136
private ComputeEngineCredentials(ComputeEngineCredentials.Builder builder) {
135137
super(builder);
136-
137138
this.transportFactory =
138139
firstNonNull(
139140
builder.getHttpTransportFactory(),
@@ -153,6 +154,11 @@ private ComputeEngineCredentials(ComputeEngineCredentials.Builder builder) {
153154
}
154155
}
155156

157+
@Override
158+
public CredentialTypeForMetrics getMetricsCredentialType() {
159+
return CredentialTypeForMetrics.VM_CREDENTIALS;
160+
}
161+
156162
/** Clones the compute engine account with the specified scopes. */
157163
@Override
158164
public GoogleCredentials createScoped(Collection<String> newScopes) {
@@ -234,7 +240,8 @@ public String getUniverseDomain() throws IOException {
234240
}
235241

236242
private String getUniverseDomainFromMetadata() throws IOException {
237-
HttpResponse response = getMetadataResponse(getUniverseDomainUrl());
243+
HttpResponse response =
244+
getMetadataResponse(getUniverseDomainUrl(), RequestType.UNTRACKED, false);
238245
int statusCode = response.getStatusCode();
239246
if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
240247
return Credentials.GOOGLE_DEFAULT_UNIVERSE;
@@ -260,7 +267,8 @@ private String getUniverseDomainFromMetadata() throws IOException {
260267
/** Refresh the access token by getting it from the GCE metadata server */
261268
@Override
262269
public AccessToken refreshAccessToken() throws IOException {
263-
HttpResponse response = getMetadataResponse(createTokenUrlWithScopes());
270+
HttpResponse response =
271+
getMetadataResponse(createTokenUrlWithScopes(), RequestType.ACCESS_TOKEN_REQUEST, true);
264272
int statusCode = response.getStatusCode();
265273
if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
266274
throw new IOException(
@@ -325,7 +333,8 @@ public IdToken idTokenWithAudience(String targetAudience, List<IdTokenProvider.O
325333
}
326334
}
327335
documentUrl.set("audience", targetAudience);
328-
HttpResponse response = getMetadataResponse(documentUrl.toString());
336+
HttpResponse response =
337+
getMetadataResponse(documentUrl.toString(), RequestType.ID_TOKEN_REQUEST, true);
329338
InputStream content = response.getContent();
330339
if (content == null) {
331340
throw new IOException("Empty content from metadata token server request.");
@@ -334,13 +343,21 @@ public IdToken idTokenWithAudience(String targetAudience, List<IdTokenProvider.O
334343
return IdToken.create(rawToken);
335344
}
336345

337-
private HttpResponse getMetadataResponse(String url) throws IOException {
346+
private HttpResponse getMetadataResponse(
347+
String url, RequestType requestType, boolean shouldSendMetricsHeader) throws IOException {
338348
GenericUrl genericUrl = new GenericUrl(url);
339349
HttpRequest request =
340350
transportFactory.create().createRequestFactory().buildGetRequest(genericUrl);
341351
JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
342352
request.setParser(parser);
343353
request.getHeaders().set(METADATA_FLAVOR, GOOGLE);
354+
// do not send metric header for getUniverseDomain and getAccount
355+
if (shouldSendMetricsHeader) {
356+
MetricsUtils.setMetricsHeader(
357+
request,
358+
MetricsUtils.getGoogleCredentialsMetricsHeader(requestType, getMetricsCredentialType()));
359+
}
360+
344361
request.setThrowExceptionOnExecuteError(false);
345362
HttpResponse response;
346363
try {
@@ -440,7 +457,10 @@ private static boolean pingComputeEngineMetadata(
440457
transportFactory.create().createRequestFactory().buildGetRequest(tokenUrl);
441458
request.setConnectTimeout(COMPUTE_PING_CONNECTION_TIMEOUT_MS);
442459
request.getHeaders().set(METADATA_FLAVOR, GOOGLE);
443-
460+
MetricsUtils.setMetricsHeader(
461+
request,
462+
MetricsUtils.getGoogleCredentialsMetricsHeader(
463+
RequestType.METADATA_SERVER_PING, CredentialTypeForMetrics.DO_NOT_SEND));
444464
HttpResponse response = request.execute();
445465
try {
446466
// Internet providers can return a generic response to all requests, so it is necessary
@@ -588,7 +608,8 @@ public byte[] sign(byte[] toSign) {
588608
}
589609

590610
private String getDefaultServiceAccount() throws IOException {
591-
HttpResponse response = getMetadataResponse(getServiceAccountsUrl());
611+
HttpResponse response =
612+
getMetadataResponse(getServiceAccountsUrl(), RequestType.UNTRACKED, false);
592613
int statusCode = response.getStatusCode();
593614
if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
594615
throw new IOException(

oauth2_http/java/com/google/auth/oauth2/IamUtils.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,11 @@
4444
import com.google.api.client.json.JsonObjectParser;
4545
import com.google.api.client.util.ExponentialBackOff;
4646
import com.google.api.client.util.GenericData;
47+
import com.google.auth.CredentialTypeForMetrics;
4748
import com.google.auth.Credentials;
4849
import com.google.auth.ServiceAccountSigner;
4950
import com.google.auth.http.HttpCredentialsAdapter;
51+
import com.google.auth.oauth2.MetricsUtils.RequestType;
5052
import com.google.common.io.BaseEncoding;
5153
import java.io.IOException;
5254
import java.io.InputStream;
@@ -178,6 +180,7 @@ private static String getSignature(
178180
* @param transport transport used for building the HTTP request
179181
* @param targetAudience the audience the issued ID token should include
180182
* @param additionalFields additional fields to send in the IAM call
183+
* @param credentialTypeForMetrics credential type for credential making this call
181184
* @return IdToken issed to the serviceAccount
182185
* @throws IOException if the IdToken cannot be issued.
183186
* @see <a
@@ -189,7 +192,8 @@ static IdToken getIdToken(
189192
HttpTransport transport,
190193
String targetAudience,
191194
boolean includeEmail,
192-
Map<String, ?> additionalFields)
195+
Map<String, ?> additionalFields,
196+
CredentialTypeForMetrics credentialTypeForMetrics)
193197
throws IOException {
194198

195199
String idTokenUrl = String.format(ID_TOKEN_URL_FORMAT, serviceAccountEmail);
@@ -211,6 +215,11 @@ static IdToken getIdToken(
211215
request.setParser(parser);
212216
request.setThrowExceptionOnExecuteError(false);
213217

218+
MetricsUtils.setMetricsHeader(
219+
request,
220+
MetricsUtils.getGoogleCredentialsMetricsHeader(
221+
RequestType.ID_TOKEN_REQUEST, credentialTypeForMetrics));
222+
214223
HttpResponse response = request.execute();
215224
int statusCode = response.getStatusCode();
216225
if (statusCode >= 400 && statusCode < HttpStatusCodes.STATUS_CODE_SERVER_ERROR) {

oauth2_http/java/com/google/auth/oauth2/ImpersonatedCredentials.java

+14-2
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,11 @@
4343
import com.google.api.client.http.json.JsonHttpContent;
4444
import com.google.api.client.json.JsonObjectParser;
4545
import com.google.api.client.util.GenericData;
46+
import com.google.auth.CredentialTypeForMetrics;
4647
import com.google.auth.ServiceAccountSigner;
4748
import com.google.auth.http.HttpCredentialsAdapter;
4849
import com.google.auth.http.HttpTransportFactory;
50+
import com.google.auth.oauth2.MetricsUtils.RequestType;
4951
import com.google.common.annotations.VisibleForTesting;
5052
import com.google.common.base.MoreObjects;
5153
import com.google.common.collect.ImmutableMap;
@@ -406,7 +408,7 @@ static ImpersonatedCredentials fromJson(
406408
.setSourceCredentials(sourceCredentials)
407409
.setTargetPrincipal(targetPrincipal)
408410
.setDelegates(delegates)
409-
.setScopes(new ArrayList<String>())
411+
.setScopes(new ArrayList<>())
410412
.setLifetime(DEFAULT_LIFETIME_IN_SECONDS)
411413
.setHttpTransportFactory(transportFactory)
412414
.setQuotaProjectId(quotaProjectId)
@@ -431,6 +433,11 @@ public GoogleCredentials createScoped(Collection<String> scopes) {
431433
.build();
432434
}
433435

436+
@Override
437+
public CredentialTypeForMetrics getMetricsCredentialType() {
438+
return CredentialTypeForMetrics.IMPERSONATED_CREDENTIALS;
439+
}
440+
434441
/**
435442
* Clones the impersonated credentials with a new calendar.
436443
*
@@ -508,6 +515,10 @@ public AccessToken refreshAccessToken() throws IOException {
508515
HttpRequest request = requestFactory.buildPostRequest(url, requestContent);
509516
adapter.initialize(request);
510517
request.setParser(parser);
518+
MetricsUtils.setMetricsHeader(
519+
request,
520+
MetricsUtils.getGoogleCredentialsMetricsHeader(
521+
RequestType.ACCESS_TOKEN_REQUEST, getMetricsCredentialType()));
511522

512523
HttpResponse response = null;
513524
try {
@@ -557,7 +568,8 @@ public IdToken idTokenWithAudience(String targetAudience, List<IdTokenProvider.O
557568
transportFactory.create(),
558569
targetAudience,
559570
includeEmail,
560-
ImmutableMap.of("delegates", this.delegates));
571+
ImmutableMap.of("delegates", this.delegates),
572+
getMetricsCredentialType());
561573
}
562574

563575
@Override

oauth2_http/java/com/google/auth/oauth2/MetricsUtils.java

+49
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,16 @@
3131

3232
package com.google.auth.oauth2;
3333

34+
import com.google.api.client.http.HttpRequest;
35+
import com.google.auth.CredentialTypeForMetrics;
3436
import java.io.IOException;
3537
import java.io.InputStream;
3638
import java.util.Properties;
3739

3840
class MetricsUtils {
3941
static final String API_CLIENT_HEADER = "x-goog-api-client";
42+
static final String CRED_TYPE = "cred-type";
43+
static final String AUTH_REQUEST_TYPE = "auth-request-type";
4044
private static final String authLibraryVersion = getAuthLibraryVersion();
4145
private static final String javaLanguageVersion = System.getProperty("java.version");
4246

@@ -67,4 +71,49 @@ private static String getAuthLibraryVersion() {
6771
}
6872
return version;
6973
}
74+
75+
public enum RequestType {
76+
ACCESS_TOKEN_REQUEST("at"),
77+
ID_TOKEN_REQUEST("it"),
78+
METADATA_SERVER_PING("mds"),
79+
UNTRACKED("untracked");
80+
81+
private String label;
82+
83+
private RequestType(String label) {
84+
this.label = label;
85+
}
86+
87+
public String getLabel() {
88+
return label;
89+
}
90+
}
91+
92+
/**
93+
* Formulates metrics header string. Header string takes format: “gl-java/JAVA_VERSION
94+
* auth/LIB_VERSION auth-request-type/REQUEST_TYPE cred-type/CREDENTIAL_TYPE”. "auth-request-type"
95+
* and "cred-type" can be omitted.
96+
*
97+
* @param requestType Auth request type to be specified in metrics, omit when {@code
98+
* RequestType.UNTRACKED}
99+
* @param credentialTypeForMetrics Credential type to be included in metrics string, omit when
100+
* {@code CredentialTypeForMetrics.DO_NOT_SEND}
101+
* @return metrics header string to send
102+
*/
103+
static String getGoogleCredentialsMetricsHeader(
104+
RequestType requestType, CredentialTypeForMetrics credentialTypeForMetrics) {
105+
StringBuilder stringBuilder =
106+
new StringBuilder(MetricsUtils.getLanguageAndAuthLibraryVersions());
107+
if (requestType != RequestType.UNTRACKED) {
108+
stringBuilder.append(String.format(" %s/%s", AUTH_REQUEST_TYPE, requestType.getLabel()));
109+
}
110+
if (credentialTypeForMetrics != CredentialTypeForMetrics.DO_NOT_SEND) {
111+
stringBuilder.append(String.format(" %s/%s", CRED_TYPE, credentialTypeForMetrics.getLabel()));
112+
}
113+
return stringBuilder.toString();
114+
}
115+
116+
static void setMetricsHeader(HttpRequest request, String metricsHeader) {
117+
request.getHeaders().set(MetricsUtils.API_CLIENT_HEADER, metricsHeader);
118+
}
70119
}

0 commit comments

Comments
 (0)