Skip to content

Commit

Permalink
chore: use gcp-metadata (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
JustinBeckwith authored Feb 8, 2018
1 parent a21d244 commit 3c9efe5
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 260 deletions.
19 changes: 18 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"@types/tmp": "0.0.33",
"clang-format": "^1.0.50",
"codecov": "^3.0.0",
"gcp-metadata": "^0.5.0",
"gh-pages": "^1.1.0",
"gts": "^0.5.3",
"js-green-licenses": "^0.4.0",
Expand Down
11 changes: 8 additions & 3 deletions src/auth/computeclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import {AxiosError, AxiosPromise, AxiosRequestConfig, AxiosResponse} from 'axios';
import {BASE_PATH, HEADER_NAME, HOST_ADDRESS} from 'gcp-metadata';

import {RequestError} from './../transporters';
import {CredentialRequest, Credentials} from './credentials';
Expand All @@ -27,7 +28,7 @@ export class Compute extends OAuth2Client {
* Google Compute Engine metadata server token endpoint.
*/
protected static readonly _GOOGLE_OAUTH2_TOKEN_URL =
'http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token';
`${BASE_PATH}/instance/service-accounts/default/token`;

/**
* Google Compute Engine service account credentials.
Expand Down Expand Up @@ -60,11 +61,15 @@ export class Compute extends OAuth2Client {
*/
protected async refreshToken(refreshToken?: string|
null): Promise<GetTokenResponse> {
const url = this.tokenUrl || Compute._GOOGLE_OAUTH2_TOKEN_URL;
const url =
this.tokenUrl || `${HOST_ADDRESS}${Compute._GOOGLE_OAUTH2_TOKEN_URL}`;
let res: AxiosResponse<CredentialRequest>|null = null;
// request for new token
try {
res = await this.transporter.request<CredentialRequest>({url});
// TODO: In 2.0, we should remove the ability to configure the tokenUrl,
// and switch this over to use the gcp-metadata package instead.
res = await this.transporter.request<CredentialRequest>(
{url, headers: {'Metadata-Flavor': 'Google'}});
} catch (e) {
e.message = 'Could not refresh access token.';
throw e;
Expand Down
63 changes: 9 additions & 54 deletions src/auth/googleauth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import {AxiosError} from 'axios';
import {exec} from 'child_process';
import * as fs from 'fs';
import * as gcpMetadata from 'gcp-metadata';
import * as os from 'os';
import * as path from 'path';
import * as stream from 'stream';
Expand Down Expand Up @@ -243,33 +244,9 @@ export class GoogleAuth {
* @returns A promise that resolves with the boolean.
* @api private
*/
async _checkIsGCE(isRetry = false): Promise<boolean> {
if (this.checkIsGCE !== undefined) {
return this.checkIsGCE;
}
if (!this.transporter) {
this.transporter = new DefaultTransporter();
}
try {
const res = await this.transporter.request(
{url: 'http://metadata.google.internal'});
this.checkIsGCE =
res && res.headers && res.headers['metadata-flavor'] === 'Google';
} catch (e) {
const isDNSError = (e as NodeJS.ErrnoException).code === 'ENOTFOUND';
const ae = e as AxiosError;
const is5xx = ae.response &&
(ae.response.status >= 500 && ae.response.status < 600);
if (is5xx) {
// Unexpected error occurred. Retry once.
if (!isRetry) {
return await this._checkIsGCE(true);
}
throw e;
} else if (!isDNSError) {
throw e;
}
this.checkIsGCE = false;
async _checkIsGCE() {
if (this.checkIsGCE === undefined) {
this.checkIsGCE = await gcpMetadata.isAvailable();
}
return this.checkIsGCE;
}
Expand Down Expand Up @@ -580,33 +557,17 @@ export class GoogleAuth {

/**
* Gets the Compute Engine project ID if it can be inferred.
* Uses 169.254.169.254 for the metadata server to avoid request
* latency from DNS lookup.
* See https://cloud.google.com/compute/docs/metadata#metadataserver
* for information about this IP address. (This IP is also used for
* Amazon EC2 instances, so the metadata flavor is crucial.)
* See https://github.com/google/oauth2client/issues/93 for context about
* DNS latency.
*
* @api private
*/
private async getGCEProjectId() {
if (!this.transporter) {
this.transporter = new DefaultTransporter();
}
try {
const r = await this.transporter.request<string>({
url: 'http://169.254.169.254/computeMetadata/v1/project/project-id',
headers: {'Metadata-Flavor': 'Google'}
});
const r = await gcpMetadata.project('project-id');
return r.data;
} catch (e) {
// Ignore any errors
return null;
}
}


/**
* The callback function handles a credential object that contains the
* client_email and private_key (if exists).
Expand Down Expand Up @@ -645,19 +606,13 @@ export class GoogleAuth {
}

// For GCE, return the service account details from the metadata server
const result = await this.transporter.request<CredentialResult>({
url:
'http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/?recursive=true',
headers: {'Metadata-Flavor': 'Google'}
});
const {data} = await gcpMetadata.instance(
{property: 'service-accounts', params: {recursive: true}});

if (!result.data || !result.data.default || !result.data.default.email) {
if (!data || !data.default || !data.default.email) {
throw new Error('Failure from metadata server.');
}

// Callback with the body
const credential:
CredentialBody = {client_email: result.data.default.email};
return credential;
return {client_email: data.default.email};
}
}
58 changes: 27 additions & 31 deletions test/test.compute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import * as assert from 'assert';
import {AxiosRequestConfig} from 'axios';
import {BASE_PATH, HOST_ADDRESS} from 'gcp-metadata';
import * as nock from 'nock';

import {Credentials} from '../src/auth/credentials';
Expand All @@ -24,6 +25,8 @@ import {Compute} from '../src/index';

nock.disableNetConnect();

const tokenPath = `${BASE_PATH}/instance/service-accounts/default/token`;

describe('Initial credentials', () => {
it('should create a dummy refresh token string', () => {
// It is important that the compute client is created with a refresh token
Expand All @@ -45,19 +48,21 @@ describe('Compute auth client', () => {
});

it('should get an access token for the first request', done => {
nock('http://metadata.google.internal')
.get('/computeMetadata/v1beta1/instance/service-accounts/default/token')
.reply(200, {access_token: 'abc123', expires_in: 10000});
nock(HOST_ADDRESS).get(tokenPath).reply(200, {
access_token: 'abc123',
expires_in: 10000
});
compute.request({url: 'http://foo'}, () => {
assert.equal(compute.credentials.access_token, 'abc123');
done();
});
});

it('should refresh if access token has expired', (done) => {
nock('http://metadata.google.internal')
.get('/computeMetadata/v1beta1/instance/service-accounts/default/token')
.reply(200, {access_token: 'abc123', expires_in: 10000});
nock(HOST_ADDRESS).get(tokenPath).reply(200, {
access_token: 'abc123',
expires_in: 10000
});
compute.credentials.access_token = 'initial-access-token';
compute.credentials.expiry_date = (new Date()).getTime() - 10000;
compute.request({url: 'http://foo'}, () => {
Expand All @@ -69,10 +74,10 @@ describe('Compute auth client', () => {
it('should refresh if access token will expired soon and time to refresh' +
' before expiration is set',
(done) => {
nock('http://metadata.google.internal')
.get(
'/computeMetadata/v1beta1/instance/service-accounts/default/token')
.reply(200, {access_token: 'abc123', expires_in: 10000});
nock(HOST_ADDRESS).get(tokenPath).reply(200, {
access_token: 'abc123',
expires_in: 10000
});
compute = new Compute({eagerRefreshThresholdMillis: 10000});
compute.credentials.access_token = 'initial-access-token';
compute.credentials.expiry_date = (new Date()).getTime() + 5000;
Expand All @@ -85,11 +90,10 @@ describe('Compute auth client', () => {
it('should not refresh if access token will not expire soon and time to' +
' refresh before expiration is set',
(done) => {
const scope =
nock('http://metadata.google.internal')
.get(
'/computeMetadata/v1beta1/instance/service-accounts/default/token')
.reply(200, {access_token: 'abc123', expires_in: 10000});
const scope = nock(HOST_ADDRESS).get(tokenPath).reply(200, {
access_token: 'abc123',
expires_in: 10000
});
compute = new Compute({eagerRefreshThresholdMillis: 1000});
compute.credentials.access_token = 'initial-access-token';
compute.credentials.expiry_date = (new Date()).getTime() + 12000;
Expand All @@ -102,11 +106,10 @@ describe('Compute auth client', () => {
});

it('should not refresh if access token has not expired', (done) => {
const scope =
nock('http://metadata.google.internal')
.get(
'/computeMetadata/v1beta1/instance/service-accounts/default/token')
.reply(200, {access_token: 'abc123', expires_in: 10000});
const scope = nock(HOST_ADDRESS).get(tokenPath).reply(200, {
access_token: 'abc123',
expires_in: 10000
});
compute.credentials.access_token = 'initial-access-token';
compute.credentials.expiry_date = (new Date()).getTime() + 10 * 60 * 1000;
compute.request({url: 'http://foo'}, () => {
Expand Down Expand Up @@ -134,10 +137,7 @@ describe('Compute auth client', () => {
};

nock('http://foo').get('/').twice().reply(403, 'a weird response body');
nock('http://metadata.google.internal')
.get(
'/computeMetadata/v1beta1/instance/service-accounts/default/token')
.reply(403, 'a weird response body');
nock(HOST_ADDRESS).get(tokenPath).reply(403, 'a weird response body');

compute.request({url: 'http://foo'}, (err, response) => {
assert(response);
Expand Down Expand Up @@ -181,9 +181,8 @@ describe('Compute auth client', () => {

it('should return a helpful message on token refresh response.statusCode 403',
(done) => {
nock('http://metadata.google.internal')
.get(
'/computeMetadata/v1beta1/instance/service-accounts/default/token')
nock(HOST_ADDRESS)
.get(tokenPath)
.twice()
.reply(403, 'a weird response body');

Expand All @@ -210,10 +209,7 @@ describe('Compute auth client', () => {

it('should return a helpful message on token refresh response.statusCode 404',
done => {
nock('http://metadata.google.internal')
.get(
'/computeMetadata/v1beta1/instance/service-accounts/default/token')
.reply(404, 'a weird body');
nock(HOST_ADDRESS).get(tokenPath).reply(404, 'a weird body');

// Mock the credentials object with a null access token, to force
// a refresh.
Expand Down
Loading

0 comments on commit 3c9efe5

Please sign in to comment.