Skip to content

Commit

Permalink
feat: adds Pluggable Auth support (#1437)
Browse files Browse the repository at this point in the history
* feat!: Adding Pluggable Auth Support to ADC (#1419)

* feat: Adding Pluggable Auth Support to ADC

See go/pluggable-auth-design.
Adding classes required for supporting pluggable auth and some functionality.
Will add the implementation for running the executable and reading from a cached file in a later pull request.

* fix: Correcting copyright year

* fix: addressing code review comments

* fix: Fixing interface description

* fix: Comment typo

* fix: Address code review comments

* fix: Add comments to ExecutableResponse properties

* fix: Addressing code review comments

* feat: adding executable and file handling for Pluggable Auth Client (#1431)

* feat: adding executable and file handling for pluggable auth client

Added PluggableAuthHandler to run user provided executable and read from cached file output + associated tests.

* fix: correcting pluggable auth credential source

Adding 'executable' object under credential source for pluggable auth options.

* fix: code review comments

* fix: Fixing output file string variable name and failing tests

* fix: code review comments + added readme documentation.

* fix: addressing code review

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* fix: addressing code review

* fix: fixing test

* fix: fixing linter warning

* fix: Adding back in readme changes that got removed by owlbot

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
Co-authored-by: Leo <39062083+lsirac@users.noreply.github.com>
  • Loading branch information
3 people authored Aug 11, 2022
1 parent f483a68 commit ed7ef7a
Show file tree
Hide file tree
Showing 13 changed files with 2,476 additions and 5 deletions.
131 changes: 130 additions & 1 deletion .readme-partials.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -476,8 +476,137 @@ body: |-
- `$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 Executable-sourced credentials with OIDC and SAML
**Executable-sourced credentials**
For executable-sourced credentials, a local executable is used to retrieve the 3rd party token.
The executable must handle providing a valid, unexpired OIDC ID token or SAML assertion in JSON format
to stdout.
To use executable-sourced credentials, the `GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES`
environment variable must be set to `1`.
To generate an executable-sourced workload identity configuration, run the following command:
```bash
# Generate a configuration file for executable-sourced credentials.
gcloud iam workload-identity-pools create-cred-config \
projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID \
--service-account=$SERVICE_ACCOUNT_EMAIL \
--subject-token-type=$SUBJECT_TOKEN_TYPE \
# The absolute path for the program, including arguments.
# e.g. --executable-command="/path/to/command --foo=bar"
--executable-command=$EXECUTABLE_COMMAND \
# Optional argument for the executable timeout. Defaults to 30s.
# --executable-timeout-millis=$EXECUTABLE_TIMEOUT \
# Optional argument for the absolute path to the executable output file.
# See below on how this argument impacts the library behaviour.
# --executable-output-file=$EXECUTABLE_OUTPUT_FILE \
--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.
- `$PROVIDER_ID`: The OIDC or SAML provider ID.
- `$SERVICE_ACCOUNT_EMAIL`: The email of the service account to impersonate.
- `$SUBJECT_TOKEN_TYPE`: The subject token type.
- `$EXECUTABLE_COMMAND`: The full command to run, including arguments. Must be an absolute path to the program.
The `--executable-timeout-millis` flag is optional. This is the duration for which
the auth library will wait for the executable to finish, in milliseconds.
Defaults to 30 seconds when not provided. The maximum allowed value is 2 minutes.
The minimum is 5 seconds.
The `--executable-output-file` flag is optional. If provided, the file path must
point to the 3PI credential response generated by the executable. This is useful
for caching the credentials. By specifying this path, the Auth libraries will first
check for its existence before running the executable. By caching the executable JSON
response to this file, it improves performance as it avoids the need to run the executable
until the cached credentials in the output file are expired. The executable must
handle writing to this file - the auth libraries will only attempt to read from
this location. The format of contents in the file should match the JSON format
expected by the executable shown below.
To retrieve the 3rd party token, the library will call the executable
using the command specified. The executable's output must adhere to the response format
specified below. It must output the response to stdout.
A sample successful executable OIDC response:
```json
{
"version": 1,
"success": true,
"token_type": "urn:ietf:params:oauth:token-type:id_token",
"id_token": "HEADER.PAYLOAD.SIGNATURE",
"expiration_time": 1620499962
}
```
A sample successful executable SAML response:
```json
{
"version": 1,
"success": true,
"token_type": "urn:ietf:params:oauth:token-type:saml2",
"saml_response": "...",
"expiration_time": 1620499962
}
```
For successful responses, the `expiration_time` field is only required
when an output file is specified in the credential configuration.
A sample executable error response:
```json
{
"version": 1,
"success": false,
"code": "401",
"message": "Caller not authorized."
}
```
These are all required fields for an error response. The code and message
fields will be used by the library as part of the thrown exception.
Response format fields summary:
* `version`: The version of the JSON output. Currently, only version 1 is supported.
* `success`: The status of the response. When true, the response must contain the 3rd party token
and token type. The response must also contain the expiration time if an output file was specified in the credential configuration.
The executable must also exit with exit code 0.
When false, the response must contain the error code and message fields and exit with a non-zero value.
* `token_type`: The 3rd party subject token type. Must be *urn:ietf:params:oauth:token-type:jwt*,
*urn:ietf:params:oauth:token-type:id_token*, or *urn:ietf:params:oauth:token-type:saml2*.
* `id_token`: The 3rd party OIDC token.
* `saml_response`: The 3rd party SAML response.
* `expiration_time`: The 3rd party subject token expiration time in seconds (unix epoch time).
* `code`: The error code string.
* `message`: The error message.
All response types must include both the `version` and `success` fields.
* Successful responses must include the `token_type` and one of
`id_token` or `saml_response`. The `expiration_time` field must also be present if an output file was specified in
the credential configuration.
* Error responses must include both the `code` and `message` fields.
The library will populate the following environment variables when the executable is run:
* `GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE`: The audience field from the credential configuration. Always present.
* `GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL`: The service account email. Only present when service account impersonation is used.
* `GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE`: The output file location from the credential configuration. Only present when specified in the credential configuration.
* `GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE`: This expected subject token type. Always present.
These environment variables can be used by the executable to avoid hard-coding these values.
##### Security considerations
The following security practices are highly recommended:
* Access to the script should be restricted as it will be displaying credentials to stdout. This ensures that rogue processes do not gain access to the script.
* The configuration file should not be modifiable. Write access should be restricted to avoid processes modifying the executable command portion.
Given the complexity of using executable-sourced credentials, it is recommended to use
the existing supported mechanisms (file-sourced/URL-sourced) for providing 3rd party
credentials unless they do not meet your specific requirements.
You can now [use the Auth library](#using-external-identities) to call Google Cloud
resources from an OIDC or SAML provider.
### Using External Identities
External identities (AWS, Azure and OIDC-based providers) can be used with `Application Default Credentials`.
Expand Down
131 changes: 130 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -520,7 +520,136 @@ Where the following variables need to be substituted:
- `$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 Executable-sourced credentials with OIDC and SAML

**Executable-sourced credentials**
For executable-sourced credentials, a local executable is used to retrieve the 3rd party token.
The executable must handle providing a valid, unexpired OIDC ID token or SAML assertion in JSON format
to stdout.

To use executable-sourced credentials, the `GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES`
environment variable must be set to `1`.

To generate an executable-sourced workload identity configuration, run the following command:

```bash
# Generate a configuration file for executable-sourced credentials.
gcloud iam workload-identity-pools create-cred-config \
projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/$POOL_ID/providers/$PROVIDER_ID \
--service-account=$SERVICE_ACCOUNT_EMAIL \
--subject-token-type=$SUBJECT_TOKEN_TYPE \
# The absolute path for the program, including arguments.
# e.g. --executable-command="/path/to/command --foo=bar"
--executable-command=$EXECUTABLE_COMMAND \
# Optional argument for the executable timeout. Defaults to 30s.
# --executable-timeout-millis=$EXECUTABLE_TIMEOUT \
# Optional argument for the absolute path to the executable output file.
# See below on how this argument impacts the library behaviour.
# --executable-output-file=$EXECUTABLE_OUTPUT_FILE \
--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.
- `$PROVIDER_ID`: The OIDC or SAML provider ID.
- `$SERVICE_ACCOUNT_EMAIL`: The email of the service account to impersonate.
- `$SUBJECT_TOKEN_TYPE`: The subject token type.
- `$EXECUTABLE_COMMAND`: The full command to run, including arguments. Must be an absolute path to the program.

The `--executable-timeout-millis` flag is optional. This is the duration for which
the auth library will wait for the executable to finish, in milliseconds.
Defaults to 30 seconds when not provided. The maximum allowed value is 2 minutes.
The minimum is 5 seconds.

The `--executable-output-file` flag is optional. If provided, the file path must
point to the 3PI credential response generated by the executable. This is useful
for caching the credentials. By specifying this path, the Auth libraries will first
check for its existence before running the executable. By caching the executable JSON
response to this file, it improves performance as it avoids the need to run the executable
until the cached credentials in the output file are expired. The executable must
handle writing to this file - the auth libraries will only attempt to read from
this location. The format of contents in the file should match the JSON format
expected by the executable shown below.

To retrieve the 3rd party token, the library will call the executable
using the command specified. The executable's output must adhere to the response format
specified below. It must output the response to stdout.

A sample successful executable OIDC response:
```json
{
"version": 1,
"success": true,
"token_type": "urn:ietf:params:oauth:token-type:id_token",
"id_token": "HEADER.PAYLOAD.SIGNATURE",
"expiration_time": 1620499962
}
```

A sample successful executable SAML response:
```json
{
"version": 1,
"success": true,
"token_type": "urn:ietf:params:oauth:token-type:saml2",
"saml_response": "...",
"expiration_time": 1620499962
}
```
For successful responses, the `expiration_time` field is only required
when an output file is specified in the credential configuration.

A sample executable error response:
```json
{
"version": 1,
"success": false,
"code": "401",
"message": "Caller not authorized."
}
```
These are all required fields for an error response. The code and message
fields will be used by the library as part of the thrown exception.

Response format fields summary:
* `version`: The version of the JSON output. Currently, only version 1 is supported.
* `success`: The status of the response. When true, the response must contain the 3rd party token
and token type. The response must also contain the expiration time if an output file was specified in the credential configuration.
The executable must also exit with exit code 0.
When false, the response must contain the error code and message fields and exit with a non-zero value.
* `token_type`: The 3rd party subject token type. Must be *urn:ietf:params:oauth:token-type:jwt*,
*urn:ietf:params:oauth:token-type:id_token*, or *urn:ietf:params:oauth:token-type:saml2*.
* `id_token`: The 3rd party OIDC token.
* `saml_response`: The 3rd party SAML response.
* `expiration_time`: The 3rd party subject token expiration time in seconds (unix epoch time).
* `code`: The error code string.
* `message`: The error message.

All response types must include both the `version` and `success` fields.
* Successful responses must include the `token_type` and one of
`id_token` or `saml_response`. The `expiration_time` field must also be present if an output file was specified in
the credential configuration.
* Error responses must include both the `code` and `message` fields.

The library will populate the following environment variables when the executable is run:
* `GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE`: The audience field from the credential configuration. Always present.
* `GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL`: The service account email. Only present when service account impersonation is used.
* `GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE`: The output file location from the credential configuration. Only present when specified in the credential configuration.
* `GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE`: This expected subject token type. Always present.

These environment variables can be used by the executable to avoid hard-coding these values.

##### Security considerations
The following security practices are highly recommended:
* Access to the script should be restricted as it will be displaying credentials to stdout. This ensures that rogue processes do not gain access to the script.
* The configuration file should not be modifiable. Write access should be restricted to avoid processes modifying the executable command portion.

Given the complexity of using executable-sourced credentials, it is recommended to use
the existing supported mechanisms (file-sourced/URL-sourced) for providing 3rd party
credentials unless they do not meet your specific requirements.

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

### Using External Identities

Expand Down
51 changes: 51 additions & 0 deletions samples/test/externalclient.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ describe('samples for external-account', () => {
const suffix = generateRandomString(10);
const configFilePath = path.join(os.tmpdir(), `config-${suffix}.json`);
const oidcTokenFilePath = path.join(os.tmpdir(), `token-${suffix}.txt`);
const executableFilePath = path.join(os.tmpdir(), `executable-${suffix}.sh`);
const auth = new GoogleAuth({
scopes: 'https://www.googleapis.com/auth/cloud-platform',
});
Expand Down Expand Up @@ -248,6 +249,9 @@ describe('samples for external-account', () => {
if (fs.existsSync(oidcTokenFilePath)) {
await unlink(oidcTokenFilePath);
}
if (fs.existsSync(executableFilePath)) {
await unlink(executableFilePath);
}
// Close any open http servers.
if (httpServer) {
httpServer.close();
Expand Down Expand Up @@ -421,4 +425,51 @@ describe('samples for external-account', () => {
// Confirm expected script output.
assert.match(output, /DNS Info:/);
});

it('should acquire ADC for PluggableAuth creds', async () => {
// Create Pluggable Auth configuration JSON file.
const config = {
type: 'external_account',
audience: AUDIENCE_OIDC,
subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
token_url: 'https://sts.googleapis.com/v1/token',
service_account_impersonation_url:
'https://iamcredentials.googleapis.com/v1/projects/' +
`-/serviceAccounts/${clientEmail}:generateAccessToken`,
credential_source: {
executable: {
command: executableFilePath,
timeout_millis: 5000,
},
},
};
await writeFile(configFilePath, JSON.stringify(config));

const expirationTime = Date.now() / 1000 + 60;
const responseJson = {
version: 1,
success: true,
expiration_time: expirationTime,
token_type: 'urn:ietf:params:oauth:token-type:jwt',
id_token: oidcToken,
};
let exeContent = '#!/bin/bash\n';
exeContent += 'echo ';
exeContent += JSON.stringify(JSON.stringify(responseJson));
exeContent += '\n';
await writeFile(executableFilePath, exeContent, {mode: 0x766});
// Run sample script with GOOGLE_APPLICATION_CREDENTIALS environment
// variable pointing to the temporarily created configuration file.
const output = await execAsync(`${process.execPath} adc`, {
env: {
...process.env,
// Set environment variable to allow pluggable auth executable to run.
GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES: 1,
// GOOGLE_APPLICATION_CREDENTIALS environment variable used for ADC.
GOOGLE_APPLICATION_CREDENTIALS: configFilePath,
},
});
// Confirm expected script output.
assert.match(output, /DNS Info:/);
});
});
2 changes: 1 addition & 1 deletion src/auth/baseexternalclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export abstract class BaseExternalAccountClient extends AuthClient {
public scopes?: string | string[];
private cachedAccessToken: CredentialsWithResponse | null;
protected readonly audience: string;
private readonly subjectTokenType: string;
protected readonly subjectTokenType: string;
private readonly serviceAccountImpersonationUrl?: string;
private readonly stsCredential: sts.StsCredentials;
private readonly clientAuth?: ClientAuthentication;
Expand Down
Loading

0 comments on commit ed7ef7a

Please sign in to comment.