|
14 | 14 |
|
15 | 15 | import {exec} from 'child_process';
|
16 | 16 | import * as fs from 'fs';
|
17 |
| -import {GaxiosOptions, GaxiosResponse} from 'gaxios'; |
| 17 | +import {GaxiosError, GaxiosOptions, GaxiosResponse} from 'gaxios'; |
18 | 18 | import * as gcpMetadata from 'gcp-metadata';
|
19 | 19 | import * as os from 'os';
|
20 | 20 | import * as path from 'path';
|
@@ -47,12 +47,13 @@ import {
|
47 | 47 | EXTERNAL_ACCOUNT_TYPE,
|
48 | 48 | BaseExternalAccountClient,
|
49 | 49 | } from './baseexternalclient';
|
50 |
| -import {AuthClient, AuthClientOptions} from './authclient'; |
| 50 | +import {AuthClient, AuthClientOptions, DEFAULT_UNIVERSE} from './authclient'; |
51 | 51 | import {
|
52 | 52 | EXTERNAL_ACCOUNT_AUTHORIZED_USER_TYPE,
|
53 | 53 | ExternalAccountAuthorizedUserClient,
|
54 | 54 | ExternalAccountAuthorizedUserClientOptions,
|
55 | 55 | } from './externalAccountAuthorizedUserClient';
|
| 56 | +import {originalOrCamelOptions} from '../util'; |
56 | 57 |
|
57 | 58 | /**
|
58 | 59 | * Defines all types of explicit clients that are determined via ADC JSON
|
@@ -131,6 +132,14 @@ const GoogleAuthExceptionMessages = {
|
131 | 132 | 'Unable to detect a Project Id in the current environment. \n' +
|
132 | 133 | 'To learn more about authentication and Google APIs, visit: \n' +
|
133 | 134 | 'https://cloud.google.com/docs/authentication/getting-started',
|
| 135 | + NO_CREDENTIALS_FOUND: |
| 136 | + 'Unable to find credentials in current environment. \n' + |
| 137 | + 'To learn more about authentication and Google APIs, visit: \n' + |
| 138 | + 'https://cloud.google.com/docs/authentication/getting-started', |
| 139 | + NO_UNIVERSE_DOMAIN_FOUND: |
| 140 | + 'Unable to detect a Universe Domain in the current environment.\n' + |
| 141 | + 'To learn more about Universe Domain retrieval, visit: \n' + |
| 142 | + 'https://cloud.google.com/compute/docs/metadata/predefined-metadata-keys', |
134 | 143 | } as const;
|
135 | 144 |
|
136 | 145 | export class GoogleAuth<T extends AuthClient = JSONClient> {
|
@@ -168,6 +177,13 @@ export class GoogleAuth<T extends AuthClient = JSONClient> {
|
168 | 177 | private scopes?: string | string[];
|
169 | 178 | private clientOptions?: AuthClientOptions;
|
170 | 179 |
|
| 180 | + /** |
| 181 | + * The cached universe domain. |
| 182 | + * |
| 183 | + * @see {@link GoogleAuth.getUniverseDomain} |
| 184 | + */ |
| 185 | + #universeDomain?: string = undefined; |
| 186 | + |
171 | 187 | /**
|
172 | 188 | * Export DefaultTransporter as a static property of the class.
|
173 | 189 | */
|
@@ -286,6 +302,42 @@ export class GoogleAuth<T extends AuthClient = JSONClient> {
|
286 | 302 | return this._findProjectIdPromise;
|
287 | 303 | }
|
288 | 304 |
|
| 305 | + async #getUniverseFromMetadataServer() { |
| 306 | + if (!(await this._checkIsGCE())) return; |
| 307 | + |
| 308 | + let universeDomain: string; |
| 309 | + |
| 310 | + try { |
| 311 | + universeDomain = await gcpMetadata.universe('universe_domain'); |
| 312 | + universeDomain ||= DEFAULT_UNIVERSE; |
| 313 | + } catch (e) { |
| 314 | + if (e instanceof GaxiosError && e.status === 404) { |
| 315 | + universeDomain = DEFAULT_UNIVERSE; |
| 316 | + } else { |
| 317 | + throw e; |
| 318 | + } |
| 319 | + } |
| 320 | + |
| 321 | + return universeDomain; |
| 322 | + } |
| 323 | + |
| 324 | + /** |
| 325 | + * Retrieves, caches, and returns the universe domain in the following order |
| 326 | + * of precedence: |
| 327 | + * - The universe domain in {@link GoogleAuth.clientOptions} |
| 328 | + * - {@link gcpMetadata.universe} |
| 329 | + * |
| 330 | + * @returns The universe domain |
| 331 | + */ |
| 332 | + async getUniverseDomain(): Promise<string> { |
| 333 | + this.#universeDomain ??= originalOrCamelOptions(this.clientOptions).get( |
| 334 | + 'universe_domain' |
| 335 | + ); |
| 336 | + this.#universeDomain ??= await this.#getUniverseFromMetadataServer(); |
| 337 | + |
| 338 | + return this.#universeDomain || DEFAULT_UNIVERSE; |
| 339 | + } |
| 340 | + |
289 | 341 | /**
|
290 | 342 | * @returns Any scopes (user-specified or default scopes specified by the
|
291 | 343 | * client library) that need to be set on the current Auth client.
|
@@ -370,30 +422,21 @@ export class GoogleAuth<T extends AuthClient = JSONClient> {
|
370 | 422 | }
|
371 | 423 |
|
372 | 424 | // Determine if we're running on GCE.
|
373 |
| - let isGCE; |
374 |
| - try { |
375 |
| - isGCE = await this._checkIsGCE(); |
376 |
| - } catch (e) { |
377 |
| - if (e instanceof Error) { |
378 |
| - e.message = `Unexpected error determining execution environment: ${e.message}`; |
| 425 | + if (await this._checkIsGCE()) { |
| 426 | + // set universe domain for Compute client |
| 427 | + if (!originalOrCamelOptions(options).get('universe_domain')) { |
| 428 | + options.universeDomain = await this.getUniverseDomain(); |
379 | 429 | }
|
380 | 430 |
|
381 |
| - throw e; |
382 |
| - } |
383 |
| - |
384 |
| - if (!isGCE) { |
385 |
| - // We failed to find the default credentials. Bail out with an error. |
386 |
| - throw new Error( |
387 |
| - 'Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.' |
| 431 | + (options as ComputeOptions).scopes = this.getAnyScopes(); |
| 432 | + return await this.prepareAndCacheADC( |
| 433 | + new Compute(options), |
| 434 | + quotaProjectIdOverride |
388 | 435 | );
|
389 | 436 | }
|
390 | 437 |
|
391 |
| - // For GCE, just return a default ComputeClient. It will take care of |
392 |
| - // the rest. |
393 |
| - (options as ComputeOptions).scopes = this.getAnyScopes(); |
394 |
| - return await this.prepareAndCacheADC( |
395 |
| - new Compute(options), |
396 |
| - quotaProjectIdOverride |
| 438 | + throw new Error( |
| 439 | + 'Could not load the default credentials. Browse to https://cloud.google.com/docs/authentication/getting-started for more information.' |
397 | 440 | );
|
398 | 441 | }
|
399 | 442 |
|
@@ -893,37 +936,31 @@ export class GoogleAuth<T extends AuthClient = JSONClient> {
|
893 | 936 | if (client instanceof BaseExternalAccountClient) {
|
894 | 937 | const serviceAccountEmail = client.getServiceAccountEmail();
|
895 | 938 | if (serviceAccountEmail) {
|
896 |
| - return {client_email: serviceAccountEmail}; |
| 939 | + return { |
| 940 | + client_email: serviceAccountEmail, |
| 941 | + universe_domain: client.universeDomain, |
| 942 | + }; |
897 | 943 | }
|
898 | 944 | }
|
899 | 945 |
|
900 | 946 | if (this.jsonContent) {
|
901 |
| - const credential: CredentialBody = { |
| 947 | + return { |
902 | 948 | client_email: (this.jsonContent as JWTInput).client_email,
|
903 | 949 | private_key: (this.jsonContent as JWTInput).private_key,
|
| 950 | + universe_domain: this.jsonContent.universe_domain, |
904 | 951 | };
|
905 |
| - return credential; |
906 |
| - } |
907 |
| - |
908 |
| - const isGCE = await this._checkIsGCE(); |
909 |
| - if (!isGCE) { |
910 |
| - throw new Error('Unknown error.'); |
911 | 952 | }
|
912 | 953 |
|
913 |
| - // For GCE, return the service account details from the metadata server |
914 |
| - // NOTE: The trailing '/' at the end of service-accounts/ is very important! |
915 |
| - // The GCF metadata server doesn't respect querystring params if this / is |
916 |
| - // not included. |
917 |
| - const data = await gcpMetadata.instance({ |
918 |
| - property: 'service-accounts/', |
919 |
| - params: {recursive: 'true'}, |
920 |
| - }); |
| 954 | + if (await this._checkIsGCE()) { |
| 955 | + const [client_email, universe_domain] = await Promise.all([ |
| 956 | + gcpMetadata.instance('service-accounts/default/email'), |
| 957 | + this.getUniverseDomain(), |
| 958 | + ]); |
921 | 959 |
|
922 |
| - if (!data || !data.default || !data.default.email) { |
923 |
| - throw new Error('Failure from metadata server.'); |
| 960 | + return {client_email, universe_domain}; |
924 | 961 | }
|
925 | 962 |
|
926 |
| - return {client_email: data.default.email}; |
| 963 | + throw new Error(GoogleAuthExceptionMessages.NO_CREDENTIALS_FOUND); |
927 | 964 | }
|
928 | 965 |
|
929 | 966 | /**
|
|
0 commit comments