-
Notifications
You must be signed in to change notification settings - Fork 382
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: add integration tests for external account clients (#1117)
- Loading branch information
1 parent
31b9cfb
commit ba91949
Showing
10 changed files
with
489 additions
and
25 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |
Oops, something went wrong.