Skip to content

Commit 5511913

Browse files
authored
feat: Add Credential Information to GoogleCredential classes (#1791)
* feat: Add additional credential info for ADC Credentials * chore: Add unit tests for setting the source * chore: Add tests for GoogleCredentials * chore: Add enums for file type * chore: Update to use the file type enum * chore: Add comment for new enum * chore: Use a passive mds check in constructor * chore: Update ComputeEngineCredentials to retrieve the default service account when retrieving token * chore: Fix logging test * chore: Update logging tests * chore: Fix lint issues * chore: Fix MDS check * fix: Test failure * chore: Fix tests * chore: Fix tests * chore: Remove MDS call in the constructor * chore: Remove sysout statement * chore: Fix tests * chore: Fix comments * chore: Address PR comments * chore: fix compilation issue * chore: Address PR comments
1 parent 5768f75 commit 5511913

24 files changed

+524
-74
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ private void init() throws IOException {
125125
this.signForApp = serviceClass.getMethod(SIGN_FOR_APP_METHOD, byte[].class);
126126
Class<?> signingResultClass = forName(SIGNING_RESULT_CLASS);
127127
this.getSignature = signingResultClass.getMethod(GET_SIGNATURE_METHOD);
128+
this.name = GoogleCredentialsInfo.APP_ENGINE_CREDENTIALS.getCredentialName();
128129
} catch (ClassNotFoundException
129130
| NoSuchMethodException
130131
| IllegalAccessException

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ public static CloudShellCredentials create(int authPort) {
7070
private CloudShellCredentials(Builder builder) {
7171
super(builder);
7272
this.authPort = builder.getAuthPort();
73+
this.name = GoogleCredentialsInfo.CLOUD_SHELL_CREDENTIALS.getCredentialName();
7374
}
7475

7576
protected int getAuthPort() {

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

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,26 @@ public class ComputeEngineCredentials extends GoogleCredentials
111111
static final int MAX_COMPUTE_PING_TRIES = 3;
112112
static final int COMPUTE_PING_CONNECTION_TIMEOUT_MS = 500;
113113

114+
private static final String METADATA_FLAVOR = "Metadata-Flavor";
115+
private static final String GOOGLE = "Google";
116+
private static final String WINDOWS = "windows";
117+
private static final String LINUX = "linux";
118+
119+
private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
120+
private static final String PARSE_ERROR_ACCOUNT = "Error parsing service account response. ";
121+
private static final long serialVersionUID = -4113476462526554235L;
122+
123+
private final String transportFactoryClassName;
124+
125+
private final Collection<String> scopes;
126+
127+
private final GoogleAuthTransport transport;
128+
private final BindingEnforcement bindingEnforcement;
129+
130+
private transient HttpTransportFactory transportFactory;
131+
132+
private String universeDomainFromMetadata = null;
133+
114134
/**
115135
* Experimental Feature.
116136
*
@@ -172,27 +192,6 @@ public String getLabel() {
172192
}
173193
}
174194

175-
private static final String METADATA_FLAVOR = "Metadata-Flavor";
176-
private static final String GOOGLE = "Google";
177-
private static final String WINDOWS = "windows";
178-
private static final String LINUX = "linux";
179-
180-
private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
181-
private static final String PARSE_ERROR_ACCOUNT = "Error parsing service account response. ";
182-
private static final long serialVersionUID = -4113476462526554235L;
183-
184-
private final String transportFactoryClassName;
185-
186-
private final Collection<String> scopes;
187-
188-
private final GoogleAuthTransport transport;
189-
private final BindingEnforcement bindingEnforcement;
190-
191-
private transient HttpTransportFactory transportFactory;
192-
private transient String serviceAccountEmail;
193-
194-
private String universeDomainFromMetadata = null;
195-
196195
/**
197196
* An internal constructor
198197
*
@@ -220,6 +219,7 @@ private ComputeEngineCredentials(ComputeEngineCredentials.Builder builder) {
220219
}
221220
this.transport = builder.getGoogleAuthTransport();
222221
this.bindingEnforcement = builder.getBindingEnforcement();
222+
this.name = GoogleCredentialsInfo.COMPUTE_ENGINE_CREDENTIALS.getCredentialName();
223223
}
224224

225225
@Override
@@ -344,6 +344,11 @@ private String getUniverseDomainFromMetadata() throws IOException {
344344
/** Refresh the access token by getting it from the GCE metadata server */
345345
@Override
346346
public AccessToken refreshAccessToken() throws IOException {
347+
// Retrieve the default service account email prior to retrieving the access token
348+
if (principal == null) {
349+
principal = getDefaultServiceAccount();
350+
}
351+
347352
HttpResponse response =
348353
getMetadataResponse(createTokenUrlWithScopes(), RequestType.ACCESS_TOKEN_REQUEST, true);
349354
int statusCode = response.getStatusCode();
@@ -688,14 +693,14 @@ public static Builder newBuilder() {
688693
@Override
689694
// todo(#314) getAccount should not throw a RuntimeException
690695
public String getAccount() {
691-
if (serviceAccountEmail == null) {
696+
if (principal == null) {
692697
try {
693-
serviceAccountEmail = getDefaultServiceAccount();
698+
principal = getDefaultServiceAccount();
694699
} catch (IOException ex) {
695700
throw new RuntimeException("Failed to get service account", ex);
696701
}
697702
}
698-
return serviceAccountEmail;
703+
return principal;
699704
}
700705

701706
/**

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,10 @@ private final GoogleCredentials getDefaultCredentialsUnsynchronized(
145145
throw new IOException("File does not exist.");
146146
}
147147
credentialsStream = readStream(credentialsFile);
148-
credentials = GoogleCredentials.fromStream(credentialsStream, transportFactory);
148+
credentials =
149+
GoogleCredentials.fromStream(credentialsStream, transportFactory)
150+
.withSource(
151+
String.format("Env Var %s set to %s", CREDENTIAL_ENV_VAR, credentialsPath));
149152
} catch (IOException e) {
150153
// Although it is also the cause, the message of the caught exception can have very
151154
// important information for diagnosing errors, so include its message in the
@@ -176,7 +179,11 @@ private final GoogleCredentials getDefaultCredentialsUnsynchronized(
176179
"Attempting to load credentials from well known file: %s",
177180
wellKnownFileLocation.getCanonicalPath()));
178181
credentialsStream = readStream(wellKnownFileLocation);
179-
credentials = GoogleCredentials.fromStream(credentialsStream, transportFactory);
182+
credentials =
183+
GoogleCredentials.fromStream(credentialsStream, transportFactory)
184+
.withSource(
185+
String.format(
186+
"Well Known File at %s", wellKnownFileLocation.getCanonicalPath()));
180187
}
181188
} catch (IOException e) {
182189
throw new IOException(
@@ -210,6 +217,15 @@ private final GoogleCredentials getDefaultCredentialsUnsynchronized(
210217
if (credentials == null) {
211218
LOGGER.log(Level.FINE, "Attempting to load credentials from GCE");
212219
credentials = tryGetComputeCredentials(transportFactory);
220+
// tryGetComputeCredentials can return a null value. This check won't set the source
221+
// if the ComputeEngineCredentials is unable to be created
222+
if (credentials != null) {
223+
credentials =
224+
credentials.withSource(
225+
String.format(
226+
"Metadata Server URL set to %s",
227+
ComputeEngineCredentials.getMetadataServerUrl(this)));
228+
}
213229
}
214230

215231
if (credentials != null) {

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,6 @@ public class ExternalAccountAuthorizedUserCredentials extends GoogleCredentials
8181

8282
private static final long serialVersionUID = -2181779590486283287L;
8383

84-
static final String EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE_TYPE =
85-
"external_account_authorized_user";
86-
8784
private final String transportFactoryClassName;
8885
private final String audience;
8986
private final String tokenUrl;
@@ -117,6 +114,9 @@ private ExternalAccountAuthorizedUserCredentials(Builder builder) {
117114
this.clientId = builder.clientId;
118115
this.clientSecret = builder.clientSecret;
119116

117+
this.name =
118+
GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getCredentialName();
119+
120120
Preconditions.checkState(
121121
getAccessToken() != null || canRefresh(),
122122
"ExternalAccountAuthorizedUserCredentials must be initialized with "

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ public abstract class ExternalAccountCredentials extends GoogleCredentials {
7171
private static final String CLOUD_PLATFORM_SCOPE =
7272
"https://www.googleapis.com/auth/cloud-platform";
7373

74-
static final String EXTERNAL_ACCOUNT_FILE_TYPE = "external_account";
7574
static final String EXECUTABLE_SOURCE_KEY = "executable";
7675

7776
static final String DEFAULT_TOKEN_URL = "https://sts.{UNIVERSE_DOMAIN}/v1/token";
@@ -214,6 +213,7 @@ protected ExternalAccountCredentials(
214213
}
215214

216215
this.metricsHandler = new ExternalAccountMetricsHandler(this);
216+
this.name = GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getCredentialName();
217217
}
218218

219219
/**
@@ -271,6 +271,8 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder)
271271
builder.metricsHandler == null
272272
? new ExternalAccountMetricsHandler(this)
273273
: builder.metricsHandler;
274+
275+
this.name = GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getCredentialName();
274276
}
275277

276278
ImpersonatedCredentials buildImpersonatedCredentials() {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public class GdchCredentials extends GoogleCredentials {
9696
this.caCertPath = builder.caCertPath;
9797
this.apiAudience = builder.apiAudience;
9898
this.lifetime = builder.lifetime;
99+
this.name = GoogleCredentialsInfo.GDCH_CREDENTIALS.getCredentialName();
99100
}
100101

101102
/**

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

Lines changed: 106 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
import com.google.common.annotations.VisibleForTesting;
4141
import com.google.common.base.MoreObjects;
4242
import com.google.common.base.MoreObjects.ToStringHelper;
43+
import com.google.common.base.Strings;
4344
import com.google.common.collect.ImmutableList;
45+
import com.google.common.collect.ImmutableMap;
4446
import com.google.errorprone.annotations.CanIgnoreReturnValue;
4547
import java.io.IOException;
4648
import java.io.InputStream;
@@ -60,9 +62,46 @@ public class GoogleCredentials extends OAuth2Credentials implements QuotaProject
6062
private static final long serialVersionUID = -1522852442442473691L;
6163

6264
static final String QUOTA_PROJECT_ID_HEADER_KEY = "x-goog-user-project";
63-
static final String USER_FILE_TYPE = "authorized_user";
64-
static final String SERVICE_ACCOUNT_FILE_TYPE = "service_account";
65-
static final String GDCH_SERVICE_ACCOUNT_FILE_TYPE = "gdch_service_account";
65+
66+
/** Internal Enum info mapping for GoogleCredential subclasses */
67+
enum GoogleCredentialsInfo {
68+
USER_CREDENTIALS("User Credentials", "authorized_user"),
69+
SERVICE_ACCOUNT_CREDENTIALS("Service Account Credentials", "service_account"),
70+
GDCH_CREDENTIALS("GDCH Credentials", "gdch_service_account"),
71+
EXTERNAL_ACCOUNT_CREDENTIALS("External Account Credentials", "external_account"),
72+
EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS(
73+
"External Account Authorized User Credentials", "external_account_authorized_user"),
74+
IMPERSONATED_CREDENTIALS("Impersonated Credentials", "impersonated_service_account"),
75+
APP_ENGINE_CREDENTIALS("App Engine Credentials", null),
76+
CLOUD_SHELL_CREDENTIALS("Cloud Shell Credentials", null),
77+
COMPUTE_ENGINE_CREDENTIALS("Compute Engine Credentials", null);
78+
79+
private final String credentialName;
80+
@Nullable private final String fileType;
81+
82+
GoogleCredentialsInfo(String credentialName, String fileType) {
83+
this.credentialName = credentialName;
84+
this.fileType = fileType;
85+
}
86+
87+
String getCredentialName() {
88+
return credentialName;
89+
}
90+
91+
@Nullable
92+
String getFileType() {
93+
return fileType;
94+
}
95+
}
96+
97+
// The following package-private fields to provide additional info for errors message
98+
// Source of the credential (e.g. env var value or well know file location)
99+
String source;
100+
// User-friendly name of the Credential class
101+
String name;
102+
// Identity of the credential
103+
// Note: This field may contain data such as serviceAccountEmail which should not be serialized
104+
transient String principal;
66105

67106
private final String universeDomain;
68107
private final boolean isExplicitUniverseDomain;
@@ -207,36 +246,36 @@ public static GoogleCredentials fromStream(
207246
throw new IOException("Error reading credentials from stream, 'type' field not specified.");
208247
}
209248

210-
if (USER_FILE_TYPE.equals(fileType)) {
249+
if (fileType.equals(GoogleCredentialsInfo.USER_CREDENTIALS.getFileType())) {
211250
return UserCredentials.fromJson(fileContents, transportFactory);
212251
}
213-
if (SERVICE_ACCOUNT_FILE_TYPE.equals(fileType)) {
252+
if (fileType.equals(GoogleCredentialsInfo.SERVICE_ACCOUNT_CREDENTIALS.getFileType())) {
214253
return ServiceAccountCredentials.fromJson(fileContents, transportFactory);
215254
}
216-
if (GDCH_SERVICE_ACCOUNT_FILE_TYPE.equals(fileType)) {
255+
if (fileType.equals(GoogleCredentialsInfo.GDCH_CREDENTIALS.getFileType())) {
217256
return GdchCredentials.fromJson(fileContents);
218257
}
219-
if (ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE.equals(fileType)) {
258+
if (fileType.equals(GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType())) {
220259
return ExternalAccountCredentials.fromJson(fileContents, transportFactory);
221260
}
222-
if (ExternalAccountAuthorizedUserCredentials.EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE_TYPE.equals(
223-
fileType)) {
261+
if (fileType.equals(
262+
GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getFileType())) {
224263
return ExternalAccountAuthorizedUserCredentials.fromJson(fileContents, transportFactory);
225264
}
226-
if (ImpersonatedCredentials.IMPERSONATED_CREDENTIALS_FILE_TYPE.equals(fileType)) {
265+
if (fileType.equals(GoogleCredentialsInfo.IMPERSONATED_CREDENTIALS.getFileType())) {
227266
return ImpersonatedCredentials.fromJson(fileContents, transportFactory);
228267
}
229268
throw new IOException(
230269
String.format(
231270
"Error reading credentials from stream, 'type' value '%s' not recognized."
232271
+ " Valid values are '%s', '%s', '%s', '%s', '%s', '%s'.",
233272
fileType,
234-
USER_FILE_TYPE,
235-
SERVICE_ACCOUNT_FILE_TYPE,
236-
GDCH_SERVICE_ACCOUNT_FILE_TYPE,
237-
ExternalAccountCredentials.EXTERNAL_ACCOUNT_FILE_TYPE,
238-
ExternalAccountAuthorizedUserCredentials.EXTERNAL_ACCOUNT_AUTHORIZED_USER_FILE_TYPE,
239-
ImpersonatedCredentials.IMPERSONATED_CREDENTIALS_FILE_TYPE));
273+
GoogleCredentialsInfo.USER_CREDENTIALS.getFileType(),
274+
GoogleCredentialsInfo.SERVICE_ACCOUNT_CREDENTIALS.getFileType(),
275+
GoogleCredentialsInfo.GDCH_CREDENTIALS.getFileType(),
276+
GoogleCredentialsInfo.EXTERNAL_ACCOUNT_CREDENTIALS.getFileType(),
277+
GoogleCredentialsInfo.EXTERNAL_ACCOUNT_AUTHORIZED_USER_CREDENTIALS.getFileType(),
278+
GoogleCredentialsInfo.IMPERSONATED_CREDENTIALS.getFileType()));
240279
}
241280

242281
/**
@@ -359,6 +398,8 @@ protected GoogleCredentials(Builder builder) {
359398
this.universeDomain = builder.getUniverseDomain();
360399
this.isExplicitUniverseDomain = true;
361400
}
401+
402+
this.source = builder.source;
362403
}
363404

364405
/**
@@ -497,9 +538,53 @@ public GoogleCredentials createDelegated(String user) {
497538
return this;
498539
}
499540

541+
/**
542+
* Internal method meant to help provide information for how certain Credential objects were
543+
* initialized by ADC (e.g. The well-known file location or env var)
544+
*/
545+
GoogleCredentials withSource(String source) {
546+
return toBuilder().setSource(source).build();
547+
}
548+
549+
/**
550+
* Provides additional information regarding credential initialization source
551+
*
552+
* <ul>
553+
* <li>credential source - Initialized via the GOOGLE_APPLICATION_CREDENTIALS env var or well
554+
* known file type
555+
* <li>credential name - The user-friendly name of the credential created
556+
* <li>principal - Identity used for the credential
557+
* </ul>
558+
*
559+
* Unknown field values (i.e. null) are not included in the mapping (e.g. ComputeCredentials may
560+
* not know the principal value until after a call to MDS is made and the field will be excluded
561+
* if `getCredentialInfo` is called prior to retrieving that value). A new map of the fields is
562+
* created on every time this method is called as fields may be updated throughout the Credential
563+
* lifecycle. This mapping is intended to provide information about the Credential that is created
564+
* via ADC. Some fields may not be known if a Credential is directly created (e.g.
565+
* `ServiceAccountCredential.fromStream(InputStream)` may not know the source of the file stream).
566+
* These fields are populated on a best effort basis.
567+
*
568+
* @return ImmutableMap of information regarding how the Credential was initialized
569+
*/
570+
public Map<String, String> getCredentialInfo() {
571+
Map<String, String> infoMap = new HashMap<>();
572+
if (!Strings.isNullOrEmpty(source)) {
573+
infoMap.put("Credential Source", source);
574+
}
575+
if (!Strings.isNullOrEmpty(name)) {
576+
infoMap.put("Credential Name", name);
577+
}
578+
if (!Strings.isNullOrEmpty(principal)) {
579+
infoMap.put("Principal", principal);
580+
}
581+
return ImmutableMap.copyOf(infoMap);
582+
}
583+
500584
public static class Builder extends OAuth2Credentials.Builder {
501585
@Nullable protected String quotaProjectId;
502586
@Nullable protected String universeDomain;
587+
@Nullable String source;
503588

504589
protected Builder() {}
505590

@@ -541,6 +626,11 @@ public String getUniverseDomain() {
541626
return this.universeDomain;
542627
}
543628

629+
Builder setSource(String source) {
630+
this.source = source;
631+
return this;
632+
}
633+
544634
@Override
545635
@CanIgnoreReturnValue
546636
public Builder setAccessToken(AccessToken token) {

0 commit comments

Comments
 (0)