Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: workload identity federation support #1131

Merged
merged 27 commits into from
Feb 6, 2021
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
3f1368d
feat: defines internal utils for OAuth client Auth and error handling…
bojeil-google Aug 7, 2020
7f8b124
feat: implements the OAuth token exchange spec based on rfc8693 (#1026)
bojeil-google Aug 11, 2020
1174e4a
feat: defines ExternalAccountClient abstract class for external_accou…
bojeil-google Aug 14, 2020
424755c
feat: adds service account impersonation to `ExternalAccountClient` (…
bojeil-google Aug 18, 2020
bbcd03d
feat: defines `IdentityPoolClient` used for K8s and Azure workloads (…
bojeil-google Aug 21, 2020
26ab5e3
feat: adds crypto utils for AWS request signing (#1046)
bojeil-google Aug 24, 2020
3020c6f
feat: implements AWS signature version 4 for signing requests (#1047)
bojeil-google Aug 28, 2020
b7075e9
feat: defines `AwsClient` used for AWS workloads (#1049)
bojeil-google Aug 29, 2020
150740e
feat: defines `ExternalAccountClient` used to instantiate external ac…
bojeil-google Sep 2, 2020
d759b09
feat: implements `BaseExternalAccountClient.getProjectId()` (#1051)
bojeil-google Sep 2, 2020
291652a
feat!: integrates external_accounts with `GoogleAuth` and ADC (#1052)
bojeil-google Sep 9, 2020
b8f14ec
chore: sync to master (#1058)
bojeil-google Sep 24, 2020
317b1d2
feat: adds text/json credential_source support to IdentityPoolClients…
bojeil-google Sep 28, 2020
8f657ce
feat: get AWS region from environment variable (#1067)
wvanderdeijl Oct 8, 2020
ec84e81
fix: makes AWS fields optional when determinable from envvars (#1081)
bojeil-google Oct 21, 2020
31b9cfb
tests: fixes broken browser tests (#1102)
bojeil-google Dec 2, 2020
ba91949
test: add integration tests for external account clients (#1117)
bojeil-google Jan 21, 2021
161983e
test: add integration tests for AWS external account clients (#1122)
bojeil-google Jan 27, 2021
42b0605
docs: add instructions for using workload identity federation (#1125)
bojeil-google Feb 2, 2021
7385313
Merge remote-tracking branch 'upstream/byoid'
bojeil-google Feb 3, 2021
9b44aa7
Fixes outdated license headers.
bojeil-google Feb 3, 2021
0723ade
Update package.json
bcoe Feb 3, 2021
954cefa
Updates `generateRandomString()`
bojeil-google Feb 4, 2021
d5645e6
Merge branch 'master' into master
bcoe Feb 5, 2021
2e75989
Fixes typos and udpates aws environment_id parsing.
bojeil-google Feb 5, 2021
6f06cb3
Fixes readability of some variables.
bojeil-google Feb 5, 2021
dfb68ea
Addresses all remaining review comments.
bojeil-google Feb 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
212 changes: 211 additions & 1 deletion README.md
Expand Up @@ -48,16 +48,19 @@ npm install google-auth-library

## Ways to authenticate
This library provides a variety of ways to authenticate to your Google services.
- [Application Default Credentials](#choosing-the-correct-credential-type-automatically) - Use Application Default Credentials when you use a single identity for all users in your application. Especially useful for applications running on Google Cloud.
- [Application Default Credentials](#choosing-the-correct-credential-type-automatically) - Use Application Default Credentials when you use a single identity for all users in your application. Especially useful for applications running on Google Cloud. Application Default Credentials also support workload identity federation to access Google Cloud resources from non-Google Cloud platforms.
- [OAuth 2](#oauth2) - Use OAuth2 when you need to perform actions on behalf of the end user.
- [JSON Web Tokens](#json-web-tokens) - Use JWT when you are using a single identity for all users. Especially useful for server->server or server->API communication.
- [Google Compute](#compute) - Directly use a service account on Google Cloud Platform. Useful for server->server or server->API communication.
- [Workload Identity Federation](#workload-identity-federation) - Use workload identity federation to access Google Cloud resources from Amazon Web Services (AWS), Microsoft Azure or any identity provider that supports OpenID Connect (OIDC).

## Application Default Credentials
This library provides an implementation of [Application Default Credentials](https://cloud.google.com/docs/authentication/getting-started)for Node.js. The [Application Default Credentials](https://cloud.google.com/docs/authentication/getting-started) provide a simple way to get authorization credentials for use in calling Google APIs.

They are best suited for cases when the call needs to have the same identity and authorization level for the application independent of the user. This is the recommended approach to authorize calls to Cloud APIs, particularly when you're building an application that uses Google Cloud Platform.

Application Default Credentials also support workload identity federation to access Google Cloud resources from non-Google Cloud platforms including Amazon Web Services (AWS), Microsoft Azure or any identity provider that supports OpenID Connect (OIDC). Workload identity federation is recommended for non-Google Cloud environments as it avoids the need to download, manage and store service account private keys locally, see: [Workload Identity Federation](#workload-identity-federation).

#### Download your Service Account Credentials JSON file

To use Application Default Credentials, You first need to download a set of JSON credentials for your project. Go to **APIs & Auth** > **Credentials** in the [Google Developers Console](https://console.cloud.google.com/) and select **Service account** from the **Add credentials** dropdown.
Expand Down Expand Up @@ -363,6 +366,213 @@ async function main() {
main().catch(console.error);
```

## Workload Identity Federation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a massive addition to the README, and I worry a little about the size. I am ok keeping it here for now (non-blocking feedback), but we need a better documentation strategy for auth. @tbpg

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bcoe also pointed out the same issue which I agree with. There are larger plans to update the official authentication docs: https://cloud.google.com/docs/authentication. Though this could be delayed until Q3. Currently, they are only updating the workload identity federation docs:

It would be a good idea to align with the overall documentation effort in the cloud docs and the GitHub repos.


Using workload identity federation, your application can access Google Cloud resources from Amazon Web Services (AWS), Microsoft Azure or any identity provider that supports OpenID Connect (OIDC).

Traditionally, applications running outside Google Cloud have used service account keys to access Google Cloud resources. Using identity federation, you can allow your workload to impersonate a service account.
This lets you access Google Cloud resources directly, eliminating the maintenance and security burden associated with service account keys.

### Accessing resources from AWS

In order to access Google Cloud resources from Amazon Web Services (AWS), the following requirements are needed:
- A workload identity pool needs to be created.
- AWS needs to be added as an identity provider in the workload identity pool (The Google [organization policy](https://cloud.google.com/iam/docs/manage-workload-identity-pools-providers#restrict) needs to allow federation from AWS).
- Permission to impersonate a service account needs to be granted to the external identity.

Follow the detailed [instructions](https://cloud.google.com/iam/docs/access-resources-aws) on how to configure workload identity federation from AWS.

After configuring the AWS provider to impersonate a service account, a credential configuration file needs to be generated.
Unlike service account credential files, the generated credential configuration file will only contain non-sensitive metadata to instruct the library on how to retrieve external subject tokens and exchange them for service account access tokens.
The configuration file can be generated by using the [gcloud CLI](https://cloud.google.com/sdk/).

To generate the AWS workload identity configuration, run the following command:

```bash
# Generate an AWS configuration file.
gcloud iam workload-identity-pools create-cred-config \
projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$AWS_PROVIDER_ID \
--service-account $SERVICE_ACCOUNT_EMAIL \
--aws \
--output-file /path/to/generated/config.json
```

Where the following variables need to be substituted:
- `$PROJECT_NUMBER`: The Google Cloud project number.
- `$POOL_ID`: The workload identity pool ID.
- `$AWS_PROVIDER_ID`: The AWS provider ID.
- `$SERVICE_ACCOUNT_EMAIL`: The email of the service account to impersonate.

This will generate the configuration file in the specified output file.

You can now [start using the Auth library](#using-external-identities) to call Google Cloud resources from AWS.

### Access resources from Microsoft Azure

In order to access Google Cloud resources from Microsoft Azure, the following requirements are needed:
- A workload identity pool needs to be created.
- Azure needs to be added as an identity provider in the workload identity pool (The Google [organization policy](https://cloud.google.com/iam/docs/manage-workload-identity-pools-providers#restrict) needs to allow federation from Azure).
- The Azure tenant needs to be configured for identity federation.
- Permission to impersonate a service account needs to be granted to the external identity.

Follow the detailed [instructions](https://cloud.google.com/iam/docs/access-resources-azure) on how to configure workload identity federation from Microsoft Azure.

After configuring the Azure provider to impersonate a service account, a credential configuration file needs to be generated.
Unlike service account credential files, the generated credential configuration file will only contain non-sensitive metadata to instruct the library on how to retrieve external subject tokens and exchange them for service account access tokens.
The configuration file can be generated by using the [gcloud CLI](https://cloud.google.com/sdk/).

To generate the Azure workload identity configuration, run the following command:

```bash
# Generate an Azure configuration file.
gcloud iam workload-identity-pools create-cred-config \
projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$AZURE_PROVIDER_ID \
--service-account $SERVICE_ACCOUNT_EMAIL \
--azure \
--output-file /path/to/generated/config.json
```

Where the following variables need to be substituted:
- `$PROJECT_NUMBER`: The Google Cloud project number.
- `$POOL_ID`: The workload identity pool ID.
- `$AZURE_PROVIDER_ID`: The Azure provider ID.
- `$SERVICE_ACCOUNT_EMAIL`: The email of the service account to impersonate.

This will generate the configuration file in the specified output file.

You can now [start using the Auth library](#using-external-identities) to call Google Cloud resources from Azure.

### Accessing resources from an OIDC identity provider

In order to access Google Cloud resources from an identity provider that supports [OpenID Connect (OIDC)](https://openid.net/connect/), the following requirements are needed:
- A workload identity pool needs to be created.
- An OIDC identity provider needs to be added in the workload identity pool (The Google [organization policy](https://cloud.google.com/iam/docs/manage-workload-identity-pools-providers#restrict) needs to allow federation from the identity provider).
- Permission to impersonate a service account needs to be granted to the external identity.

Follow the detailed [instructions](https://cloud.google.com/iam/docs/access-resources-oidc) on how to configure workload identity federation from an OIDC identity provider.

After configuring the OIDC provider to impersonate a service account, a credential configuration file needs to be generated.
Unlike service account credential files, the generated credential configuration file will only contain non-sensitive metadata to instruct the library on how to retrieve external subject tokens and exchange them for service account access tokens.
The configuration file can be generated by using the [gcloud CLI](https://cloud.google.com/sdk/).

For OIDC providers, the Auth library can retrieve OIDC tokens either from a local file location (file-sourced credentials) or from a local server (URL-sourced credentials).

**File-sourced credentials**
For file-sourced credentials, a background process needs to be continuously refreshing the file location with a new OIDC token prior to expiration.
For tokens with one hour lifetimes, the token needs to be updated in the file every hour. The token can be stored directly as plain text or in JSON format.

To generate a file-sourced OIDC configuration, run the following command:

```bash
# Generate an OIDC configuration file for file-sourced credentials.
gcloud iam workload-identity-pools create-cred-config \
projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$OIDC_PROVIDER_ID \
--service-account $SERVICE_ACCOUNT_EMAIL \
--credential-source-file $PATH_TO_OIDC_ID_TOKEN \
# Optional arguments for file types. Default is "text":
# --credential-source-type "json" \
# Optional argument for the field that contains the OIDC credential.
# This is required for json.
# --credential-source-field-name "id_token" \
--output-file /path/to/generated/config.json
```

Where the following variables need to be substituted:
- `$PROJECT_NUMBER`: The Google Cloud project number.
- `$POOL_ID`: The workload identity pool ID.
- `$OIDC_PROVIDER_ID`: The OIDC provider ID.
- `$SERVICE_ACCOUNT_EMAIL`: The email of the service account to impersonate.
- `$PATH_TO_OIDC_ID_TOKEN`: The file path where the OIDC token will be retrieved from.

This will generate the configuration file in the specified output file.

**URL-sourced credentials**
For URL-sourced credentials, a local server needs to host a GET endpoint to return the OIDC token. The response can be in plain text or JSON.
Additional required request headers can also be specified.

To generate a URL-sourced OIDC workload identity configuration, run the following command:

```bash
# Generate an OIDC configuration file for URL-sourced credentials.
gcloud iam workload-identity-pools create-cred-config \
projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$OIDC_PROVIDER_ID \
--service-account $SERVICE_ACCOUNT_EMAIL \
--credential-source-url $URL_TO_GET_OIDC_TOKEN \
--credential-source-headers $HEADER_KEY=$HEADER_VALUE \
# Optional arguments for file types. Default is "text":
# --credential-source-type "json" \
# Optional argument for the field that contains the OIDC credential.
# This is required for json.
# --credential-source-field-name "id_token" \
--output-file /path/to/generated/config.json
```

Where the following variables need to be substituted:
- `$PROJECT_NUMBER`: The Google Cloud project number.
- `$POOL_ID`: The workload identity pool ID.
- `$OIDC_PROVIDER_ID`: The OIDC provider ID.
- `$SERVICE_ACCOUNT_EMAIL`: The email of the service account to impersonate.
- `$URL_TO_GET_OIDC_TOKEN`: The URL of the local server endpoint to call to retrieve the OIDC token.
- `$HEADER_KEY` and `$HEADER_VALUE`: The additional header key/value pairs to pass along the GET request to `$URL_TO_GET_OIDC_TOKEN`, e.g. `Metadata-Flavor=Google`.

You can now [start using the Auth library](#using-external-identities) to call Google Cloud resources from an OIDC provider.

### Using External Identities

External identities (AWS, Azure and OIDC-based providers) can be used with `Application Default Credentials`.
In order to use external identities with Application Default Credentials, you need to generate the JSON credentials configuration file for your external identity as described above.
Once generated, store the path to this file in the `GOOGLE_APPLICATION_CREDENTIALS` environment variable.

```bash
export GOOGLE_APPLICATION_CREDENTIALS=/path/to/config.json
```

The library can now automatically choose the right type of client and initialize credentials from the context provided in the configuration file.

```js
async function main() {
const auth = new GoogleAuth({
scopes: 'https://www.googleapis.com/auth/cloud-platform'
});
const client = await auth.getClient();
const projectId = await auth.getProjectId();
// List all buckets in a project.
const url = `https://storage.googleapis.com/storage/v1/b?project=${projectId}`;
const res = await client.request({ url });
console.log(res.data);
}
```

When using external identities with Application Default Credentials in Node.js, the `roles/browser` role needs to be granted to the service account.
The `Cloud Resource Manager API` should also be enabled on the project.
This is needed since the library will try to auto-discover the project ID from the current environment using the impersonated credential.
To avoid this requirement, the project ID can be explicitly specified on initialization.

```js
const auth = new GoogleAuth({
scopes: 'https://www.googleapis.com/auth/cloud-platform',
// Pass the project ID explicitly to avoid the need to grant `roles/browser` to the service account
// or enable Cloud Resource Manager API on the project.
projectId: 'CLOUD_RESOURCE_PROJECT_ID',
});
```

You can also explicitly initialize external account clients using the generated configuration file.

```js
const {ExternalAccountClient} = require('google-auth-library');
const jsonConfig = require('/path/to/config.json');

async function main() {
const client = ExternalAccountClient.fromJSON(jsonConfig);
client.scopes = ['https://www.googleapis.com/auth/cloud-platform'];
// List all buckets in a project.
const url = `https://storage.googleapis.com/storage/v1/b?project=${projectId}`;
const res = await client.request({url});
console.log(res.data);
}
```

## Working with ID Tokens
### Fetching ID Tokens
If your application is running on Cloud Run or Cloud Functions, or using Cloud Identity-Aware
Expand Down
54 changes: 53 additions & 1 deletion browser-test/test.crypto.ts
Expand Up @@ -14,7 +14,7 @@

import * as base64js from 'base64-js';
import {assert} from 'chai';
import {createCrypto} from '../src/crypto/crypto';
import {createCrypto, fromArrayBufferToHex} from '../src/crypto/crypto';
import {BrowserCrypto} from '../src/crypto/browser/crypto';
import {privateKey, publicKey} from './fixtures/keys';
import {describe, it} from 'mocha';
Expand Down Expand Up @@ -99,4 +99,56 @@ describe('Browser crypto tests', () => {
const encodedString = crypto.encodeBase64StringUtf8(originalString);
assert.strictEqual(encodedString, base64String);
});

it('should calculate SHA256 digest in hex encoding', async () => {
const input = 'I can calculate SHA256';
const expectedHexDigest =
'73d08486d8bfd4fb4bc12dd8903604ddbde5ad95b6efa567bd723ce81a881122';

const calculatedHexDigest = await crypto.sha256DigestHex(input);
assert.strictEqual(calculatedHexDigest, expectedHexDigest);
});

describe('should compute the HMAC-SHA256 hash of a message', () => {
it('using a string key', async () => {
const message = 'The quick brown fox jumps over the lazy dog';
const key = 'key';
const expectedHexHash =
'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8';
const extectedHash = new Uint8Array(
alexander-fenster marked this conversation as resolved.
Show resolved Hide resolved
(expectedHexHash.match(/.{1,2}/g) as string[]).map(byte =>
parseInt(byte, 16)
)
);

const calculatedHash = await crypto.signWithHmacSha256(key, message);
assert.deepStrictEqual(calculatedHash, extectedHash.buffer);
});

it('using an ArrayBuffer key', async () => {
const message = 'The quick brown fox jumps over the lazy dog';
// String "key" ArrayBuffer representation.
const key = new Uint8Array([107, 0, 101, 0, 121, 0])
.buffer as ArrayBuffer;
const expectedHexHash =
'f7bc83f430538424b13298e6aa6fb143ef4d59a14946175997479dbc2d1a3cd8';
const extectedHash = new Uint8Array(
(expectedHexHash.match(/.{1,2}/g) as string[]).map(byte =>
parseInt(byte, 16)
)
);

const calculatedHash = await crypto.signWithHmacSha256(key, message);
assert.deepStrictEqual(calculatedHash, extectedHash.buffer);
});
});

it('should expose a method to convert an ArrayBuffer to hex', () => {
const arrayBuffer = new Uint8Array([4, 8, 0, 12, 16, 0])
.buffer as ArrayBuffer;
const expectedHexEncoding = '0408000c1000';

const calculatedHexEncoding = fromArrayBufferToHex(arrayBuffer);
assert.strictEqual(calculatedHexEncoding, expectedHexEncoding);
});
});
7 changes: 4 additions & 3 deletions package.json
Expand Up @@ -29,6 +29,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 Down Expand Up @@ -67,9 +69,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 +84,7 @@
"fix": "gts fix",
"pretest": "npm run compile",
"docs": "compodoc src/",
"samples-setup": "cd samples/ && npm link ../ && npm run setup && cd ../",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a reasonable addition to the script, presumably so that you could setup the sample environment outside of running tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that is correct. This is very useful if you want to set up the sample environment in a different project or locally.

"samples-test": "cd samples/ && npm link ../ && npm test && cd ../",
"system-test": "mocha build/system-test --timeout 60000",
"presystem-test": "npm run compile",
Expand Down
1 change: 1 addition & 0 deletions samples/package.json
Expand Up @@ -5,6 +5,7 @@
"*.js"
],
"scripts": {
"setup": "node scripts/externalclient-setup.js",
"test": "mocha --timeout 60000"
},
"engines": {
Expand Down