Skip to content

Commit

Permalink
test: add integration tests for external account clients (#1117)
Browse files Browse the repository at this point in the history
  • Loading branch information
bojeil-google authored Jan 21, 2021
1 parent 31b9cfb commit ba91949
Show file tree
Hide file tree
Showing 10 changed files with 489 additions and 25 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

[1]: https://www.npmjs.com/package/google-auth-library-nodejs?activeTab=versions

### [6.1.4](https://www.github.com/googleapis/google-auth-library-nodejs/compare/v6.1.3...v6.1.4) (2020-12-22)


### Bug Fixes

* move accessToken to headers instead of parameter ([#1108](https://www.github.com/googleapis/google-auth-library-nodejs/issues/1108)) ([67b0cc3](https://www.github.com/googleapis/google-auth-library-nodejs/commit/67b0cc3077860a1583bcf18ce50aeff58bbb5496))

### [6.1.3](https://www.github.com/googleapis/google-auth-library-nodejs/compare/v6.1.2...v6.1.3) (2020-10-22)


Expand Down
13 changes: 11 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ accept your pull requests.
1. Title your pull request following [Conventional Commits](https://www.conventionalcommits.org/) styling.
1. Submit a pull request.

### Before you begin

1. [Select or create a Cloud Platform project][projects].
1. [Set up authentication with a service account][auth] so you can access the
API from your local workstation.


## Running the tests

1. [Prepare your environment for Node.js setup][setup].
Expand All @@ -51,15 +58,17 @@ accept your pull requests.
npm test

# Run sample integration tests.
gcloud auth application-default login
npm run samples-test

# Run all system tests.
gcloud auth application-default login
npm run system-test

1. Lint (and maybe fix) any changes:

npm run fix

[setup]: https://cloud.google.com/nodejs/docs/setup
[projects]: https://console.cloud.google.com/project
[billing]: https://support.google.com/cloud/answer/6293499#enable-billing

[auth]: https://cloud.google.com/docs/authentication/getting-started
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,8 +445,7 @@ A complete example can be found in [`samples/verifyIdToken-iap.js`](https://gith

## Samples

Samples are in the [`samples/`](https://github.com/googleapis/google-auth-library-nodejs/tree/master/samples) directory. The samples' `README.md`
has instructions for running the samples.
Samples are in the [`samples/`](https://github.com/googleapis/google-auth-library-nodejs/tree/master/samples) directory. Each sample's `README.md` has instructions for running its sample.

| Sample | Source Code | Try it |
| --------------------------- | --------------------------------- | ------ |
Expand Down
12 changes: 7 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "google-auth-library",
"version": "6.1.3",
"version": "6.1.4",
"author": "Google Inc.",
"description": "Google APIs Authentication Client Library for Node.js",
"engines": {
Expand All @@ -17,6 +17,7 @@
"client library"
],
"dependencies": {
"12": "^1.0.2",
"arrify": "^2.0.0",
"base64-js": "^1.3.0",
"ecdsa-sig-formatter": "^1.0.11",
Expand All @@ -29,6 +30,8 @@
},
"devDependencies": {
"@compodoc/compodoc": "^1.1.7",
"@microsoft/api-documenter": "^7.8.10",
"@microsoft/api-extractor": "^7.8.10",
"@types/base64-js": "^1.2.5",
"@types/chai": "^4.1.7",
"@types/jws": "^3.1.0",
Expand All @@ -43,7 +46,7 @@
"c8": "^7.0.0",
"chai": "^4.2.0",
"codecov": "^3.0.2",
"execa": "^4.0.0",
"execa": "^5.0.0",
"gts": "^2.0.0",
"is-docker": "^2.0.0",
"karma": "^5.0.0",
Expand All @@ -67,9 +70,7 @@
"ts-loader": "^8.0.0",
"typescript": "^3.8.3",
"webpack": "^4.20.2",
"webpack-cli": "^4.0.0",
"@microsoft/api-documenter": "^7.8.10",
"@microsoft/api-extractor": "^7.8.10"
"webpack-cli": "^4.0.0"
},
"files": [
"build/src",
Expand All @@ -84,6 +85,7 @@
"fix": "gts fix",
"pretest": "npm run compile",
"docs": "compodoc src/",
"samples-setup": "cd samples/ && npm link ../ && npm run setup && cd ../",
"samples-test": "cd samples/ && npm link ../ && npm test && cd ../",
"system-test": "mocha build/system-test --timeout 60000",
"presystem-test": "npm run compile",
Expand Down
3 changes: 2 additions & 1 deletion samples/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
"*.js"
],
"scripts": {
"setup": "node scripts/externalclient-setup.js",
"test": "mocha --timeout 60000"
},
"engines": {
"node": ">=10"
},
"license": "Apache-2.0",
"dependencies": {
"google-auth-library": "^6.1.3",
"google-auth-library": "^6.1.4",
"node-fetch": "^2.3.0",
"opn": "^5.3.0",
"server-destroy": "^1.0.1"
Expand Down
201 changes: 201 additions & 0 deletions samples/scripts/externalclient-setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// Copyright 2021 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// This script is used to generate the project configurations needed to
// end-to-end test workload identity pools in the Auth library, specifically
// file-sourced and URL-sourced OIDC-based credentials.
// This is done via the sample test: samples/test/externalclient.test.js.
//
// In order to run this script, the GOOGLE_APPLICATION_CREDENTIALS environment
// variable needs to be set to point to a service account key file.
//
// The following IAM roles need to be set on the service account:
// 1. IAM Workload Identity Pool Admin (needed to create resources for workload
// identity pools).
// 2. Security Admin (needed to get and set IAM policies).
// 3. Service Account Token Creator (needed to generate Google ID tokens and
// access tokens).
//
// The following APIs need to be enabled on the project:
// 1. Identity and Access Management (IAM) API.
// 2. IAM Service Account Credentials API.
// 3. Cloud Resource Manager API.
// 4. The API being accessed in the test, eg. DNS.
//
// This script needs to be run once. It will do the following:
// 1. Create a random workload identity pool.
// 2. Create a random OIDC provider in that pool which uses the
// accounts.google.com as the issuer and the default STS audience as the
// allowed audience. This audience will be validated on STS token exchange.
// 3. Enable OIDC tokens generated by the current service account to impersonate
// the service account. (Identified by the OIDC token sub field which is the
// service account client ID).
// 4. Print out the STS audience field associated with the created provider
// after the setup completes successfully so that it can be used in the
// tests. This will be copied and used as the global AUDIENCE constant in
// samples/test/externalclient.test.js.
// The same service account used for this setup script should be used for
// the test script.
// It is safe to run the setup script again. A new pool is created and a new
// audience is printed. If run multiple times, it is advisable to delete
// unused pools. Note that deleted pools are soft deleted and may remain for
// a while before they are completely deleted. The old pool ID cannot be used
// in the meantime.

const fs = require('fs');
const {promisify} = require('util');
const {GoogleAuth} = require('google-auth-library');

const readFile = promisify(fs.readFile);

/**
* Generates a random string of the specified length, optionally using the
* specified alphabet.
*
* @param {number} length The length of the string to generate.
* @return {string} A random string of the provided length.
*/
const generateRandomString = length => {
const chars = [];
const allowedChars = 'abcdefghijklmnopqrstuvwxyz0123456789';
while (length > 0) {
chars.push(
allowedChars.charAt(Math.floor(Math.random() * allowedChars.length))
);
length--;
}
return chars.join('');
};

/**
* Creates a workload identity pool with an OIDC provider which will accept
* Google OIDC tokens generated from the current service account where the token
* will have sub as the service account client ID and the audience as the
* created identity pool STS audience.
* The steps followed here mirror the instructions for configuring federation
* with an OIDC provider illustrated at:
* https://cloud.google.com/iam/docs/access-resources-oidc
* @return {Promise<string>} A promise that resolves with the STS audience
* corresponding with the generated workload identity pool OIDC provider.
*/
async function main() {
let response;
const keyFile = process.env.GOOGLE_APPLICATION_CREDENTIALS;
if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
throw new Error('No GOOGLE_APPLICATION_CREDENTIALS env var is available');
}
const keys = JSON.parse(await readFile(keyFile, 'utf8'));
const suffix = generateRandomString(10);
const poolId = `pool-${suffix}`;
const providerId = `oidc-${suffix}`;
const projectId = keys.project_id;
const clientEmail = keys.client_email;
const sub = keys.client_id;
const auth = new GoogleAuth({
scopes: 'https://www.googleapis.com/auth/cloud-platform',
});

// TODO: switch to using IAM client SDK once v1 API has all the v1beta
// changes.
// https://cloud.google.com/iam/docs/reference/rest/v1beta/projects.locations.workloadIdentityPools
// https://github.com/googleapis/google-api-nodejs-client/tree/master/src/apis/iam

// Create the workload identity pool.
response = await auth.request({
url:
`https://iam.googleapis.com/v1beta/projects/${projectId}/` +
`locations/global/workloadIdentityPools?workloadIdentityPoolId=${poolId}`,
method: 'POST',
data: {
displayName: 'Test workload identity pool',
description: 'Test workload identity pool for Node.js',
},
});
// Populate the audience field. This will be used by the tests, specifically
// the credential configuration file.
const poolResourcePath = response.data.name.split('/operations')[0];
const aud = `//iam.googleapis.com/${poolResourcePath}/providers/${providerId}`;

// Allow service account impersonation.
// Get the existing IAM policity bindings on the current service account.
response = await auth.request({
url:
`https://iam.googleapis.com/v1/projects/${projectId}/` +
`serviceAccounts/${clientEmail}:getIamPolicy`,
method: 'POST',
});
const bindings = response.data.bindings || [];
// If not found, add roles/iam.workloadIdentityUser role binding to the
// workload identity pool member. We will use the value mapped to
// google.subject.
// This is the sub field of the OIDC token which is the service account
// client_id.
let found = false;
bindings.forEach(binding => {
if (binding.role === 'roles/iam.workloadIdentityUser') {
found = true;
binding.members = [
`principal://iam.googleapis.com/${poolResourcePath}/subject/${sub}`,
];
}
});
if (!found) {
bindings.push({
role: 'roles/iam.workloadIdentityUser',
members: [
`principal://iam.googleapis.com/${poolResourcePath}/subject/${sub}`,
],
});
}
await auth.request({
url:
`https://iam.googleapis.com/v1/projects/${projectId}/` +
`serviceAccounts/${clientEmail}:setIamPolicy`,
method: 'POST',
data: {
policy: {
bindings,
},
},
});

// Create an OIDC provider. This will use the accounts.google.com issuer URL.
// This will use the STS audience as the OIDC token audience.
await auth.request({
url:
`https://iam.googleapis.com/v1beta/projects/${projectId}/` +
`locations/global/workloadIdentityPools/${poolId}/providers?` +
`workloadIdentityPoolProviderId=${providerId}`,
method: 'POST',
data: {
displayName: 'Test OIDC provider',
description: 'Test OIDC provider for Node.js',
attributeMapping: {
'google.subject': 'assertion.sub',
},
oidc: {
issuerUri: 'https://accounts.google.com',
allowedAudiences: [aud],
},
},
});
return aud;
}

// On execution, the generated audience will be printed to the screen.
// This should be used as the STS audeince in test/externalclient.test.js.
// Some delay is needed before running the tests in test/externalclient.test.js
// to ensure IAM policies propagate before running sample tests.
// Normally 1-2 minutes should suffice.
main().then(console.log).catch(console.error);
Loading

0 comments on commit ba91949

Please sign in to comment.