Skip to content

Commit

Permalink
feat!: workload identity federation support (#1131)
Browse files Browse the repository at this point in the history
feat: implements the OAuth token exchange spec based on rfc8693 (#1026)
feat: defines ExternalAccountClient abstract class for external_account credentials (#1030)
feat: adds service account impersonation to `ExternalAccountClient` (#1041)
feat: defines `IdentityPoolClient` used for K8s and Azure workloads (#1042)
feat: implements AWS signature version 4 for signing requests (#1047)
feat: defines `ExternalAccountClient` used to instantiate external account clients (#1050)
feat!: integrates external_accounts with `GoogleAuth` and ADC (#1052)
feat: adds text/json credential_source support to IdentityPoolClients (#1059)
feat: get AWS region from environment variable (#1067)
Co-authored-by: Wilfred van der Deijl <wilfred@vanderdeijl.com>
Co-authored-by: Benjamin E. Coe <bencoe@google.com>
  • Loading branch information
3 people committed Feb 6, 2021
1 parent 02d0d73 commit 997f124
Show file tree
Hide file tree
Showing 34 changed files with 9,202 additions and 50 deletions.
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

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 expectedHash = new Uint8Array(
(expectedHexHash.match(/.{1,2}/g) as string[]).map(byte =>
parseInt(byte, 16)
)
);

const calculatedHash = await crypto.signWithHmacSha256(key, message);
assert.deepStrictEqual(calculatedHash, expectedHash.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 expectedHash = new Uint8Array(
(expectedHexHash.match(/.{1,2}/g) as string[]).map(byte =>
parseInt(byte, 16)
)
);

const calculatedHash = await crypto.signWithHmacSha256(key, message);
assert.deepStrictEqual(calculatedHash, expectedHash.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 ../",
"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

0 comments on commit 997f124

Please sign in to comment.