Skip to content

Commit cb39908

Browse files
authored
Merge branch 'main' into refactor-x509-provider
2 parents f78ed98 + 7909924 commit cb39908

23 files changed

Lines changed: 594 additions & 78 deletions

oauth2_http/java/com/google/auth/mtls/MtlsHttpTransportFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
package com.google.auth.mtls;
3333

3434
import com.google.api.client.http.javanet.NetHttpTransport;
35+
import com.google.api.core.InternalApi;
3536
import com.google.auth.http.HttpTransportFactory;
3637
import java.security.GeneralSecurityException;
3738
import java.security.KeyStore;
@@ -45,6 +46,7 @@
4546
* <p><b>Warning:</b> This class is considered internal and is not intended for direct use by
4647
* library consumers. Its API and behavior may change without notice.
4748
*/
49+
@InternalApi
4850
public class MtlsHttpTransportFactory implements HttpTransportFactory {
4951
private final KeyStore mtlsKeyStore;
5052

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ public int hashCode() {
114114
return Objects.hash(tokenValue, expirationTimeMillis, scopes);
115115
}
116116

117+
/**
118+
* Returns a string representation of this access token, including the raw token value.
119+
*
120+
* <p><b>Security Warning:</b> The output of this method includes the raw, unmasked access token
121+
* value. Do not log this output in production environments as it may expose sensitive
122+
* credentials.
123+
*/
117124
@Override
118125
public String toString() {
119126
return MoreObjects.toStringHelper(this)

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

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@
5151
import com.google.common.base.Joiner;
5252
import com.google.common.base.MoreObjects.ToStringHelper;
5353
import com.google.common.collect.ImmutableList;
54-
import com.google.common.collect.ImmutableMap;
5554
import com.google.common.collect.ImmutableSet;
5655
import com.google.errorprone.annotations.CanIgnoreReturnValue;
5756
import java.io.BufferedReader;
@@ -129,6 +128,7 @@ public class ComputeEngineCredentials extends GoogleCredentials
129128
private transient HttpTransportFactory transportFactory;
130129

131130
private String universeDomainFromMetadata = null;
131+
private String projectId = null;
132132

133133
/**
134134
* Experimental Feature.
@@ -340,6 +340,81 @@ private String getUniverseDomainFromMetadata() throws IOException {
340340
return responseString;
341341
}
342342

343+
/**
344+
* Retrieves the Google Cloud project ID from the Compute Engine (GCE) metadata server.
345+
*
346+
* <p>On its first successful execution, it fetches the project ID and caches it for the lifetime
347+
* of the object. Subsequent calls will return the cached value without making additional network
348+
* requests.
349+
*
350+
* <p>If the request to the metadata server fails (e.g., due to network issues, or if the VM lacks
351+
* the required service account permissions), the method will attempt to fall back to a default
352+
* project ID provider which could be {@code null}.
353+
*
354+
* @return the GCP project ID string, or {@code null} if the metadata server is inaccessible and
355+
* no fallback project ID can be determined.
356+
*/
357+
@Override
358+
public String getProjectId() {
359+
synchronized (this) {
360+
if (this.projectId != null) {
361+
return this.projectId;
362+
}
363+
}
364+
365+
String projectIdFromMetadata = getProjectIdFromMetadata();
366+
synchronized (this) {
367+
// Check first if another thread set the Project ID. No need to overwrite
368+
// if a Projects ID already exists. Tries to prevent a case where the last call
369+
// for `getProjectIdFromMetadata()` returns null and overwrites valid data.
370+
if (this.projectId == null) {
371+
this.projectId = projectIdFromMetadata;
372+
}
373+
}
374+
return this.projectId;
375+
}
376+
377+
private String getProjectIdFromMetadata() {
378+
try {
379+
HttpResponse response = getMetadataResponse(getProjectIdUrl(), RequestType.UNTRACKED, false);
380+
int statusCode = response.getStatusCode();
381+
if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
382+
LoggingUtils.log(
383+
LOGGER_PROVIDER,
384+
Level.WARNING,
385+
Collections.emptyMap(),
386+
String.format(
387+
"Error code %s trying to get project ID from"
388+
+ " Compute Engine metadata. This may be because the virtual machine instance"
389+
+ " does not have permission scopes specified.",
390+
statusCode));
391+
return super.getProjectId();
392+
}
393+
if (statusCode != HttpStatusCodes.STATUS_CODE_OK) {
394+
LoggingUtils.log(
395+
LOGGER_PROVIDER,
396+
Level.WARNING,
397+
Collections.emptyMap(),
398+
String.format(
399+
"Unexpected Error code %s trying to get project ID"
400+
+ " from Compute Engine metadata for the default service account: %s",
401+
statusCode, response.parseAsString()));
402+
return super.getProjectId();
403+
}
404+
return response.parseAsString();
405+
} catch (IOException e) {
406+
LoggingUtils.log(
407+
LOGGER_PROVIDER,
408+
Level.WARNING,
409+
Collections.emptyMap(),
410+
String.format(
411+
"Unexpected Error: %s trying to get project ID"
412+
+ " from Compute Engine metadata server. Reason: %s",
413+
e.getMessage(), e.getCause().toString()));
414+
return super.getProjectId();
415+
}
416+
}
417+
343418
/** Refresh the access token by getting it from the GCE metadata server */
344419
@Override
345420
public AccessToken refreshAccessToken() throws IOException {
@@ -435,11 +510,9 @@ public IdToken idTokenWithAudience(String targetAudience, List<IdTokenProvider.O
435510
}
436511
String rawToken = response.parseAsString();
437512

438-
LoggingUtils.log(
439-
LOGGER_PROVIDER,
440-
Level.FINE,
441-
ImmutableMap.of("idToken", rawToken),
442-
"Response Payload for ID token");
513+
GenericData idTokenData = new GenericData();
514+
idTokenData.set("id_token", rawToken);
515+
LoggingUtils.logResponsePayload(idTokenData, LOGGER_PROVIDER, "Response Payload for ID token");
443516
return IdToken.create(rawToken);
444517
}
445518

@@ -448,6 +521,9 @@ private HttpResponse getMetadataResponse(
448521
GenericUrl genericUrl = new GenericUrl(url);
449522
HttpRequest request =
450523
transportFactory.create().createRequestFactory().buildGetRequest(genericUrl);
524+
// Disable automatic logging by google-http-java-client to prevent leakage of sensitive tokens.
525+
// Client Library Debug Logging via LoggingUtils is used instead where appropriate.
526+
request.setLoggingEnabled(false);
451527
JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
452528
request.setParser(parser);
453529
request.getHeaders().set(METADATA_FLAVOR, GOOGLE);
@@ -461,17 +537,17 @@ private HttpResponse getMetadataResponse(
461537
request.setThrowExceptionOnExecuteError(false);
462538
HttpResponse response;
463539
try {
464-
String requestMessage;
465-
String responseMessage;
540+
String requestMessage = null;
541+
String responseMessage = null;
466542
if (requestType.equals(RequestType.ID_TOKEN_REQUEST)) {
467543
requestMessage = "Sending request to get ID token";
468544
responseMessage = "Received response for ID token request";
469545
} else if (requestType.equals(RequestType.ACCESS_TOKEN_REQUEST)) {
470546
requestMessage = "Sending request to refresh access token";
471547
responseMessage = "Received response for refresh access token";
472548
} else {
473-
// TODO: this includes get universe domain and get default sa.
474-
// refactor for more clear logging message.
549+
// TODO: this includes get universe domain and get default sa. Refactor for more clear
550+
// logging message.
475551
requestMessage = "Sending request for universe domain/default service account";
476552
responseMessage = "Received response for universe domain/default service account";
477553
}
@@ -564,13 +640,21 @@ static boolean checkStaticGceDetection(DefaultCredentialsProvider provider) {
564640
return false;
565641
}
566642

643+
@VisibleForTesting
644+
void setProjectId(String projectId) {
645+
this.projectId = projectId;
646+
}
647+
567648
private static boolean pingComputeEngineMetadata(
568649
HttpTransportFactory transportFactory, DefaultCredentialsProvider provider) {
569650
GenericUrl tokenUrl = new GenericUrl(getMetadataServerUrl(provider));
570651
for (int i = 1; i <= MAX_COMPUTE_PING_TRIES; ++i) {
571652
try {
572653
HttpRequest request =
573654
transportFactory.create().createRequestFactory().buildGetRequest(tokenUrl);
655+
// Disable automatic logging by google-http-java-client. This is a ping request
656+
// and does not need to be logged by LoggingUtils.
657+
request.setLoggingEnabled(false);
574658
request.setConnectTimeout(COMPUTE_PING_CONNECTION_TIMEOUT_MS);
575659
request.getHeaders().set(METADATA_FLAVOR, GOOGLE);
576660
MetricsUtils.setMetricsHeader(
@@ -642,6 +726,11 @@ public static String getIdentityDocumentUrl() {
642726
+ "/computeMetadata/v1/instance/service-accounts/default/identity";
643727
}
644728

729+
public static String getProjectIdUrl() {
730+
return getMetadataServerUrl(DefaultCredentialsProvider.DEFAULT)
731+
+ "/computeMetadata/v1/project/project-id";
732+
}
733+
645734
@Override
646735
public int hashCode() {
647736
return Objects.hash(transportFactoryClassName);

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@
7575
* </pre>
7676
*/
7777
public class ExternalAccountAuthorizedUserCredentials extends GoogleCredentials {
78+
private static final LoggerProvider LOGGER_PROVIDER =
79+
LoggerProvider.forClazz(ExternalAccountAuthorizedUserCredentials.class);
7880

7981
private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
8082

@@ -191,13 +193,19 @@ public AccessToken refreshAccessToken() throws IOException {
191193
HttpResponse response;
192194
try {
193195
HttpRequest httpRequest = buildRefreshRequest();
196+
LoggingUtils.logRequest(
197+
httpRequest, LOGGER_PROVIDER, "Sending request to refresh access token");
194198
response = httpRequest.execute();
199+
LoggingUtils.logResponse(
200+
response, LOGGER_PROVIDER, "Received response for refresh access token");
195201
} catch (HttpResponseException e) {
196202
throw OAuthException.createFromHttpResponseException(e);
197203
}
198204

199205
// Parse response.
200206
GenericData responseData = response.parseAs(GenericData.class);
207+
LoggingUtils.logResponsePayload(
208+
responseData, LOGGER_PROVIDER, "Response payload for refresh access token");
201209
response.disconnect();
202210

203211
// Required fields.
@@ -276,6 +284,13 @@ public int hashCode() {
276284
quotaProjectId);
277285
}
278286

287+
/**
288+
* Returns a string representation of this credential.
289+
*
290+
* <p><b>Security Warning:</b> The output of this method includes sensitive fields such as the
291+
* client secret, refresh token, and request metadata containing the raw Bearer access token. Do
292+
* not log this output in production environments as it may expose sensitive credentials.
293+
*/
279294
@Override
280295
public String toString() {
281296
return MoreObjects.toStringHelper(this)
@@ -379,6 +394,9 @@ private HttpRequest buildRefreshRequest() throws IOException {
379394
.create()
380395
.createRequestFactory()
381396
.buildPostRequest(new GenericUrl(tokenUrl), new UrlEncodedContent(tokenRequest));
397+
// Disable automatic logging by google-http-java-client to prevent leakage of sensitive tokens.
398+
// Client Library Debug Logging via LoggingUtils is used instead.
399+
request.setLoggingEnabled(false);
382400

383401
request.setParser(new JsonObjectParser(JSON_FACTORY));
384402

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
import java.util.Objects;
6666

6767
public class GdchCredentials extends GoogleCredentials {
68+
private static final LoggerProvider LOGGER_PROVIDER =
69+
LoggerProvider.forClazz(GdchCredentials.class);
6870
static final String SUPPORTED_FORMAT_VERSION = "1";
6971
private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
7072
private static final int DEFAULT_LIFETIME_IN_SECONDS = 3600;
@@ -260,14 +262,20 @@ public AccessToken refreshAccessToken() throws IOException {
260262

261263
HttpRequestFactory requestFactory = transportFactory.create().createRequestFactory();
262264
HttpRequest request = requestFactory.buildPostRequest(new GenericUrl(tokenServerUri), content);
265+
// Disable automatic logging by google-http-java-client to prevent leakage of sensitive tokens.
266+
// Client Library Debug Logging via LoggingUtils is used instead.
267+
request.setLoggingEnabled(false);
263268

264269
request.setParser(new JsonObjectParser(jsonFactory));
265270

266271
HttpResponse response;
267272
String errorTemplate = "Error getting access token for GDCH service account: %s, iss: %s";
268273

269274
try {
275+
LoggingUtils.logRequest(request, LOGGER_PROVIDER, "Sending request to get GDCH access token");
270276
response = request.execute();
277+
LoggingUtils.logResponse(
278+
response, LOGGER_PROVIDER, "Received response for GDCH access token");
271279
} catch (HttpResponseException re) {
272280
String message = String.format(errorTemplate, re.getMessage(), getServiceIdentityName());
273281
throw GoogleAuthException.createWithTokenEndpointResponseException(re, message);
@@ -277,6 +285,8 @@ public AccessToken refreshAccessToken() throws IOException {
277285
}
278286

279287
GenericData responseData = response.parseAs(GenericData.class);
288+
LoggingUtils.logResponsePayload(
289+
responseData, LOGGER_PROVIDER, "Response payload for GDCH access token");
280290
String accessToken =
281291
OAuth2Utils.validateString(responseData, "access_token", PARSE_ERROR_PREFIX);
282292
int expiresInSeconds =

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ private static String getSignature(
135135

136136
HttpRequest request = factory.buildPostRequest(genericUrl, signContent);
137137

138+
// Disable automatic logging by google-http-java-client to prevent leakage of sensitive tokens.
139+
// Client Library Debug Logging via LoggingUtils is used instead.
140+
request.setLoggingEnabled(false);
141+
138142
JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
139143
request.setParser(parser);
140144
request.setThrowExceptionOnExecuteError(false);
@@ -232,6 +236,10 @@ static IdToken getIdToken(
232236
HttpRequest request =
233237
transport.createRequestFactory(adapter).buildPostRequest(genericUrl, idTokenContent);
234238

239+
// Disable automatic logging by google-http-java-client to prevent leakage of sensitive tokens.
240+
// Client Library Debug Logging via LoggingUtils is used instead.
241+
request.setLoggingEnabled(false);
242+
235243
JsonObjectParser parser = new JsonObjectParser(OAuth2Utils.JSON_FACTORY);
236244
request.setParser(parser);
237245
request.setThrowExceptionOnExecuteError(false);

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,9 @@ public AccessToken refreshAccessToken() throws IOException {
628628

629629
HttpContent requestContent = new JsonHttpContent(parser.getJsonFactory(), body);
630630
HttpRequest request = requestFactory.buildPostRequest(url, requestContent);
631+
// Disable automatic logging by google-http-java-client to prevent leakage of sensitive tokens.
632+
// Client Library Debug Logging via LoggingUtils is used instead.
633+
request.setLoggingEnabled(false);
631634
request.setConnectTimeout(connectTimeout);
632635
request.setReadTimeout(readTimeout);
633636
adapter.initialize(request);
@@ -707,6 +710,13 @@ public int hashCode() {
707710
iamEndpointOverride);
708711
}
709712

713+
/**
714+
* Returns a string representation of this credential.
715+
*
716+
* <p><b>Security Warning:</b> The output of this method includes the source credentials which may
717+
* recursively contain sensitive fields such as access tokens. Do not log this output in
718+
* production environments as it may expose sensitive credentials.
719+
*/
710720
@Override
711721
public String toString() {
712722
return MoreObjects.toStringHelper(this)

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,10 @@ private String retrieveResource(
205205
HttpRequest request =
206206
requestFactory.buildRequest(requestMethod, new GenericUrl(url), content);
207207

208+
// Disable automatic logging by google-http-java-client to prevent leakage of sensitive
209+
// metadata responses.
210+
request.setLoggingEnabled(false);
211+
208212
HttpHeaders requestHeaders = request.getHeaders();
209213
for (Map.Entry<String, Object> header : headers.entrySet()) {
210214
requestHeaders.set(header.getKey(), header.getValue());

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,12 @@ static void logResponsePayload(
7676
}
7777
}
7878

79-
// generic log method to use when not logging standard request, response and payload
79+
/**
80+
* Generic log method to use when not logging standard request, response and payload.
81+
*
82+
* <p>Any key in the provided {@code contextMap} that matches the sensitive keys set (e.g.
83+
* access_token, refresh_token) will have its value masked via SHA-256 hash before being logged.
84+
*/
8085
static void log(
8186
LoggerProvider loggerProvider, Level level, Map<String, Object> contextMap, String message) {
8287
if (loggingEnabled) {

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,14 @@ protected Map<String, List<String>> getRequestMetadataInternal() {
446446
return null;
447447
}
448448

449+
/**
450+
* Returns a string representation of this credential, including request metadata and access
451+
* token.
452+
*
453+
* <p><b>Security Warning:</b> The output of this method includes the request metadata which
454+
* contains the raw Bearer access token, and the raw access token value. Do not log this output in
455+
* production environments as it may expose sensitive credentials.
456+
*/
449457
@Override
450458
public String toString() {
451459
OAuthValue localValue = value;

0 commit comments

Comments
 (0)