diff --git a/package.json b/package.json index 2bc32c04..14965eac 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "ecdsa-sig-formatter": "^1.0.11", "gaxios": "^6.1.1", "gcp-metadata": "^6.1.0", + "google-logging-utils": "next", "gtoken": "^7.0.0", "jws": "^4.0.0" }, diff --git a/src/auth/authclient.ts b/src/auth/authclient.ts index b9de5c95..b56207fb 100644 --- a/src/auth/authclient.ts +++ b/src/auth/authclient.ts @@ -19,6 +19,7 @@ import {DefaultTransporter, Transporter} from '../transporters'; import {Credentials} from './credentials'; import {GetAccessTokenResponse, Headers} from './oauth2client'; import {OriginalAndCamel, originalOrCamelOptions} from '../util'; +import {log as makeLog} from 'google-logging-utils'; /** * Base auth configurations (e.g. from JWT or `.json` files) with conventional @@ -189,6 +190,7 @@ export abstract class AuthClient eagerRefreshThresholdMillis = DEFAULT_EAGER_REFRESH_THRESHOLD_MILLIS; forceRefreshOnFailure = false; universeDomain = DEFAULT_UNIVERSE; + log = makeLog('auth'); constructor(opts: AuthClientOptions = {}) { super(); diff --git a/src/auth/baseexternalclient.ts b/src/auth/baseexternalclient.ts index 26436979..023af9f1 100644 --- a/src/auth/baseexternalclient.ts +++ b/src/auth/baseexternalclient.ts @@ -478,13 +478,19 @@ export abstract class BaseExternalAccountClient extends AuthClient { } else if (projectNumber) { // Preferable not to use request() to avoid retrial policies. const headers = await this.getRequestHeaders(); + const url = `${this.cloudResourceManagerURL.toString()}${projectNumber}`; + const request = { + headers, + url, + }; + this.log.info('getProjectId %j', request); const response = await this.transporter.request({ + ...request, ...BaseExternalAccountClient.RETRY_CONFIG, - headers, - url: `${this.cloudResourceManagerURL.toString()}${projectNumber}`, responseType: 'json', }); this.projectId = response.data.projectId; + this.log.info('getProjectId, id %s', this.projectId); return this.projectId; } return null; @@ -665,10 +671,8 @@ export abstract class BaseExternalAccountClient extends AuthClient { private async getImpersonatedAccessToken( token: string ): Promise { - const opts: GaxiosOptions = { - ...BaseExternalAccountClient.RETRY_CONFIG, + const request = { url: this.serviceAccountImpersonationUrl!, - method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, @@ -677,11 +681,23 @@ export abstract class BaseExternalAccountClient extends AuthClient { scope: this.getScopesArray(), lifetime: this.serviceAccountImpersonationLifetime + 's', }, + }; + this.log.info('getImpersonatedAccessToken %j', request); + const opts: GaxiosOptions = { + ...request, + ...BaseExternalAccountClient.RETRY_CONFIG, + method: 'POST', responseType: 'json', }; const response = await this.transporter.request(opts); const successResponse = response.data; + this.log.info( + 'getImpersonatedAccessToken success: %s, %s, %s', + successResponse.accessToken, + successResponse.expireTime, + response + ); return { access_token: successResponse.accessToken, // Convert from ISO format to timestamp. diff --git a/src/auth/defaultawssecuritycredentialssupplier.ts b/src/auth/defaultawssecuritycredentialssupplier.ts index 12d48d3f..559d0001 100644 --- a/src/auth/defaultawssecuritycredentialssupplier.ts +++ b/src/auth/defaultawssecuritycredentialssupplier.ts @@ -18,6 +18,7 @@ import {Transporter} from '../transporters'; import {AwsSecurityCredentialsSupplier} from './awsclient'; import {AwsSecurityCredentials} from './awsrequestsigner'; import {Headers} from './oauth2client'; +import {log as makeLog} from 'google-logging-utils'; /** * Interface defining the AWS security-credentials endpoint response. @@ -82,6 +83,8 @@ export class DefaultAwsSecurityCredentialsSupplier private readonly imdsV2SessionTokenUrl?: string; private readonly additionalGaxiosOptions?: GaxiosOptions; + private log = makeLog('auth'); + /** * Instantiates a new DefaultAwsSecurityCredentialsSupplier using information * from the credential_source stored in the ADC file. @@ -122,14 +125,19 @@ export class DefaultAwsSecurityCredentialsSupplier '"options.credential_source.region_url"' ); } + const request = { + url: this.regionUrl, + headers: metadataHeaders, + }; + this.log.info('getAwsRegion %j', request); const opts: GaxiosOptions = { + ...request, ...this.additionalGaxiosOptions, - url: this.regionUrl, method: 'GET', responseType: 'text', - headers: metadataHeaders, }; const response = await context.transporter.request(opts); + this.log.info('getAwsRegion is %s', response.data); // Remove last character. For example, if us-east-2b is returned, // the region would be us-east-2. return response.data.substr(0, response.data.length - 1); @@ -186,14 +194,19 @@ export class DefaultAwsSecurityCredentialsSupplier async #getImdsV2SessionToken( transporter: Transporter | Gaxios ): Promise { + const request = { + url: this.imdsV2SessionTokenUrl, + headers: {'x-aws-ec2-metadata-token-ttl-seconds': '300'}, + }; const opts: GaxiosOptions = { + ...request, ...this.additionalGaxiosOptions, - url: this.imdsV2SessionTokenUrl, method: 'PUT', responseType: 'text', - headers: {'x-aws-ec2-metadata-token-ttl-seconds': '300'}, }; + this.log.info('#getImdsV2SessionToken %j', request); const response = await transporter.request(opts); + this.log.info('#getImdsV2SessionToken is %s', response.data); return response.data; } @@ -213,14 +226,19 @@ export class DefaultAwsSecurityCredentialsSupplier '"options.credential_source.url"' ); } + const request = { + url: this.securityCredentialsUrl, + headers: headers, + }; + this.log.info('#getAwsRoleName %j', request); const opts: GaxiosOptions = { + ...request, ...this.additionalGaxiosOptions, - url: this.securityCredentialsUrl, method: 'GET', responseType: 'text', - headers: headers, }; const response = await transporter.request(opts); + this.log.info('#getAwsRoleName name is %s', response.data); return response.data; } @@ -238,12 +256,17 @@ export class DefaultAwsSecurityCredentialsSupplier headers: Headers, transporter: Transporter | Gaxios ): Promise { + const request = { + url: `${this.securityCredentialsUrl}/${roleName}`, + headers: headers, + }; + this.log.info('#retrieveAwsSecurityCredentials %j', request); const response = await transporter.request({ + ...request, ...this.additionalGaxiosOptions, - url: `${this.securityCredentialsUrl}/${roleName}`, responseType: 'json', - headers: headers, }); + this.log.info('#retrieveAwsSecurityCredentials %s', response.data); return response.data; } diff --git a/src/auth/externalAccountAuthorizedUserClient.ts b/src/auth/externalAccountAuthorizedUserClient.ts index 24a480c0..18b6cad4 100644 --- a/src/auth/externalAccountAuthorizedUserClient.ts +++ b/src/auth/externalAccountAuthorizedUserClient.ts @@ -124,16 +124,24 @@ class ExternalAccountAuthorizedUserHandler extends OAuthClientAuthHandler { // Apply OAuth client authentication. this.applyClientAuthenticationOptions(opts); + this.log.info('refreshToken %j', { + url: opts.url, + headers: opts.headers, + data: opts.data, + }); + try { const response = await this.transporter.request(opts); // Successful response. const tokenRefreshResponse = response.data; tokenRefreshResponse.res = response; + this.log.info('refreshToken response %j', tokenRefreshResponse); return tokenRefreshResponse; } catch (error) { // Translate error to OAuthError. if (error instanceof GaxiosError && error.response) { + this.log.error('refreshToken failed %j', error.response?.data); throw getErrorFromOAuthErrorResponse( error.response.data as OAuthErrorResponse, // Preserve other fields from the original error. diff --git a/src/auth/oauth2client.ts b/src/auth/oauth2client.ts index fb813a7f..5e8f40e7 100644 --- a/src/auth/oauth2client.ts +++ b/src/auth/oauth2client.ts @@ -708,14 +708,21 @@ export class OAuth2Client extends AuthClient { if (this.clientAuthentication === ClientAuthentication.ClientSecretPost) { values.client_secret = this._clientSecret; } - const res = await this.transporter.request({ - ...OAuth2Client.RETRY_CONFIG, - method: 'POST', + + const request = { url, data: querystring.stringify(values), headers, + }; + this.log.info('getTokenAsync %j', request); + + const res = await this.transporter.request({ + ...request, + ...OAuth2Client.RETRY_CONFIG, + method: 'POST', }); const tokens = res.data as Credentials; + this.log.info('getTokenAsync success %j', tokens); if (res.data && res.data.expires_in) { tokens.expiry_date = new Date().getTime() + res.data.expires_in * 1000; delete (tokens as CredentialRequest).expires_in; @@ -769,18 +776,24 @@ export class OAuth2Client extends AuthClient { grant_type: 'refresh_token', }; + const request = { + url, + data: querystring.stringify(data), + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + }; + this.log.info('refreshTokenNoCache %j', request); + let res: GaxiosResponse; try { // request for new token res = await this.transporter.request({ + ...request, ...OAuth2Client.RETRY_CONFIG, method: 'POST', - url, - data: querystring.stringify(data), - headers: {'Content-Type': 'application/x-www-form-urlencoded'}, }); - } catch (e) { + } catch (exc) { + const e = exc as Error; if ( e instanceof GaxiosError && e.message === 'invalid_grant' && @@ -789,11 +802,13 @@ export class OAuth2Client extends AuthClient { ) { e.message = JSON.stringify(e.response.data); } + this.log.error('refreshTokenNoCache failure %j', e.message); throw e; } const tokens = res.data as Credentials; + this.log.info('refreshTokenNoCache success %j', tokens); // TODO: de-duplicate this code from a few spots if (res.data && res.data.expires_in) { tokens.expiry_date = new Date().getTime() + res.data.expires_in * 1000; @@ -1002,12 +1017,17 @@ export class OAuth2Client extends AuthClient { url: this.getRevokeTokenURL(token).toString(), method: 'POST', }; + this.log.info('revokeToken %s', opts.url); if (callback) { - this.transporter - .request(opts) - .then(r => callback(null, r), callback); + this.transporter.request(opts).then(r => { + this.log.info('revokeToken success %s', r.data ?? ''); + callback(null, r); + }, callback); } else { - return this.transporter.request(opts); + return this.transporter.request(opts).then(r => { + this.log.info('revokeToken success %j', r.data); + return r; + }); } } @@ -1271,14 +1291,22 @@ export class OAuth2Client extends AuthClient { throw new Error(`Unsupported certificate format ${format}`); } try { + this.log.info('getFederatedSignonCertsAsync %s', url); res = await this.transporter.request({ ...OAuth2Client.RETRY_CONFIG, url, }); - } catch (e) { + this.log.info( + 'getFederatedSignonCertsAsync success %j %j', + res?.data, + res?.headers + ); + } catch (err) { + const e = err as Error; if (e instanceof Error) { e.message = `Failed to retrieve verification certificates: ${e.message}`; } + this.log.error('getFederatedSignonCertsAsync failed', e?.message); throw e; } @@ -1342,14 +1370,18 @@ export class OAuth2Client extends AuthClient { const url = this.endpoints.oauth2IapPublicKeyUrl.toString(); try { + this.log.info('getIapPublicKeysAsync %s', url); res = await this.transporter.request({ ...OAuth2Client.RETRY_CONFIG, url, }); - } catch (e) { + this.log.info('getIapPublicKeysAsync success %j', res.data); + } catch (err) { + const e = err as Error; if (e instanceof Error) { e.message = `Failed to retrieve verification certificates: ${e.message}`; } + this.log.error('getIapPublicKeysAsync failed', e?.message); throw e; } diff --git a/src/auth/oauth2common.ts b/src/auth/oauth2common.ts index 19f4fe8e..0d02e754 100644 --- a/src/auth/oauth2common.ts +++ b/src/auth/oauth2common.ts @@ -14,6 +14,7 @@ import {GaxiosOptions} from 'gaxios'; import * as querystring from 'querystring'; +import {log as makeLog} from 'google-logging-utils'; import {Crypto, createCrypto} from '../crypto/crypto'; @@ -69,6 +70,7 @@ export interface ClientAuthentication { */ export abstract class OAuthClientAuthHandler { private crypto: Crypto; + log = makeLog('auth'); /** * Instantiates an OAuth client authentication handler. diff --git a/src/auth/refreshclient.ts b/src/auth/refreshclient.ts index 93c07d49..998b4391 100644 --- a/src/auth/refreshclient.ts +++ b/src/auth/refreshclient.ts @@ -80,13 +80,11 @@ export class UserRefreshClient extends OAuth2Client { } async fetchIdToken(targetAudience: string): Promise { - const res = await this.transporter.request({ - ...UserRefreshClient.RETRY_CONFIG, + const request = { url: this.endpoints.oauth2TokenUrl, headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, - method: 'POST', data: stringify({ client_id: this._clientId, client_secret: this._clientSecret, @@ -94,8 +92,17 @@ export class UserRefreshClient extends OAuth2Client { refresh_token: this._refreshToken, target_audience: targetAudience, }), + }; + this.log.info('fetchIdToken %j', request); + + const res = await this.transporter.request({ + ...request, + ...UserRefreshClient.RETRY_CONFIG, + method: 'POST', }); + this.log.info('fetchIdToken success %s', res?.data?.id_token); + return res.data.id_token!; } diff --git a/src/auth/stscredentials.ts b/src/auth/stscredentials.ts index 291da246..a6aff436 100644 --- a/src/auth/stscredentials.ts +++ b/src/auth/stscredentials.ts @@ -194,14 +194,20 @@ export class StsCredentials extends OAuthClientAuthHandler { // Inject additional STS headers if available. Object.assign(headers, additionalHeaders || {}); - const opts: GaxiosOptions = { - ...StsCredentials.RETRY_CONFIG, + const request = { url: this.tokenExchangeEndpoint.toString(), - method: 'POST', headers, data: querystring.stringify( values as unknown as querystring.ParsedUrlQueryInput ), + }; + + this.log.info('exchangeToken %j', request); + + const opts: GaxiosOptions = { + ...request, + ...StsCredentials.RETRY_CONFIG, + method: 'POST', responseType: 'json', }; // Apply OAuth client authentication. @@ -211,10 +217,13 @@ export class StsCredentials extends OAuthClientAuthHandler { const response = await this.transporter.request(opts); // Successful response. + this.log.info('exchangeToken success %j', response.data); const stsSuccessfulResponse = response.data; stsSuccessfulResponse.res = response; return stsSuccessfulResponse; } catch (error) { + this.log.error('exchangeToken failure %j', error); + // Translate error to OAuthError. if (error instanceof GaxiosError && error.response) { throw getErrorFromOAuthErrorResponse( diff --git a/src/auth/urlsubjecttokensupplier.ts b/src/auth/urlsubjecttokensupplier.ts index 9ae01f75..6771488e 100644 --- a/src/auth/urlsubjecttokensupplier.ts +++ b/src/auth/urlsubjecttokensupplier.ts @@ -14,6 +14,7 @@ import {ExternalAccountSupplierContext} from './baseexternalclient'; import {GaxiosOptions} from 'gaxios'; +import {log as makeLog} from 'google-logging-utils'; import { SubjectTokenFormatType, SubjectTokenJsonResponse, @@ -59,6 +60,7 @@ export class UrlSubjectTokenSupplier implements SubjectTokenSupplier { private readonly formatType: SubjectTokenFormatType; private readonly subjectTokenFieldName?: string; private readonly additionalGaxiosOptions?: GaxiosOptions; + private readonly log = makeLog('auth'); /** * Instantiates a URL subject token supplier. @@ -82,11 +84,16 @@ export class UrlSubjectTokenSupplier implements SubjectTokenSupplier { async getSubjectToken( context: ExternalAccountSupplierContext ): Promise { + const request = { + url: this.url, + headers: this.headers, + }; + this.log.info('getSubjectToken %j', request); + const opts: GaxiosOptions = { + ...request, ...this.additionalGaxiosOptions, - url: this.url, method: 'GET', - headers: this.headers, responseType: this.formatType, }; let subjectToken: string | undefined; @@ -99,10 +106,13 @@ export class UrlSubjectTokenSupplier implements SubjectTokenSupplier { subjectToken = response.data[this.subjectTokenFieldName]; } if (!subjectToken) { + this.log.error('getSubjectToken failed'); throw new Error( 'Unable to parse the subject_token from the credential_source URL' ); } + + this.log.info('getSubjectToken success %s', subjectToken); return subjectToken; } }