diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 6e51b0a..0967b64 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -29,16 +29,14 @@ jobs:
- name: 'npm test'
run: 'npm run test'
- credentials_file:
- name: 'credentials_file'
- permissions:
- id-token: 'write'
- contents: 'read'
- runs-on: '${{ matrix.operating-system }}'
+
+ credentials_json:
+ name: 'credentials_json'
+ runs-on: '${{ matrix.os }}'
strategy:
fail-fast: false
matrix:
- operating-system:
+ os:
- 'ubuntu-latest'
- 'windows-latest'
- 'macos-latest'
@@ -50,66 +48,62 @@ jobs:
with:
node-version: '12.x'
- - name: 'build'
- run: |-
- npm ci
- npm run build
-
- - uses: 'google-github-actions/setup-gcloud@master'
- with:
- project_id: 'actions-oidc-test'
-
- - id: 'auth'
- name: 'auth'
+ - id: 'auth-default'
+ name: 'auth-default'
uses: './'
with:
- create_credentials_file: true
- workload_identity_provider: 'projects/469401941463/locations/global/workloadIdentityPools/github-actions/providers/google-github-actions'
- service_account: 'github-secret-accessor@actions-oidc-test.iam.gserviceaccount.com'
+ credentials_json: '${{ secrets.GOOGLE_CREDENTIALS }}'
+
+ - id: 'setup-gcloud'
+ name: 'setup-gcloud'
+ uses: 'google-github-actions/setup-gcloud@master'
- id: 'gcloud'
name: 'gcloud'
+ shell: 'bash'
run: |-
- gcloud auth login --brief --cred-file="${{ steps.auth.outputs.credentials_file_path }}"
gcloud secrets versions access "latest" --secret "my-secret"
- access_token:
- name: 'access_token'
- permissions:
- id-token: 'write'
- contents: 'read'
- runs-on: 'ubuntu-latest'
- strategy:
- fail-fast: false
-
- steps:
- - uses: 'actions/checkout@v2'
-
- - uses: 'actions/setup-node@v2'
- with:
- node-version: '12.x'
-
- - name: 'build'
- run: |-
- npm ci
- npm run build
-
- - id: 'auth'
- name: 'auth'
+ - id: 'auth-access-token'
+ name: 'auth-access-token'
uses: './'
with:
+ credentials_json: '${{ secrets.GOOGLE_CREDENTIALS }}'
token_format: 'access_token'
- workload_identity_provider: 'projects/469401941463/locations/global/workloadIdentityPools/github-actions/providers/google-github-actions'
- service_account: 'github-secret-accessor@actions-oidc-test.iam.gserviceaccount.com'
- id_token:
- name: 'id_token'
- permissions:
- id-token: 'write'
- contents: 'read'
- runs-on: 'ubuntu-latest'
+ - id: 'access-token'
+ name: 'access-token'
+ shell: 'bash'
+ run: |-
+ curl https://secretmanager.googleapis.com/v1/projects/${{ steps.auth-access-token.outputs.project_id }}/secrets/my-secret/versions/latest:access \
+ --silent \
+ --show-error \
+ --fail \
+ --header "Authorization: Bearer ${{ steps.auth-access-token.outputs.access_token }}"
+
+ - id: 'auth-id-token'
+ name: 'auth-id-token'
+ uses: './'
+ with:
+ credentials_json: '${{ secrets.GOOGLE_CREDENTIALS }}'
+ token_format: 'id_token'
+ id_token_audience: 'https://secretmanager.googleapis.com/'
+ id_token_include_email: true
+
+
+ workload_identity_federation:
+ name: 'workload_identity_federation'
+ runs-on: '${{ matrix.os }}'
strategy:
fail-fast: false
+ matrix:
+ os:
+ - 'ubuntu-latest'
+ - 'windows-latest'
+ - 'macos-latest'
+
+ permissions:
+ id-token: 'write'
steps:
- uses: 'actions/checkout@v2'
@@ -118,17 +112,47 @@ jobs:
with:
node-version: '12.x'
- - name: 'build'
- run: |-
- npm ci
- npm run build
-
- - id: 'auth'
- name: 'auth'
+ - id: 'auth-default'
+ name: 'auth-default'
uses: './'
with:
- token_format: 'id_token'
workload_identity_provider: 'projects/469401941463/locations/global/workloadIdentityPools/github-actions/providers/google-github-actions'
service_account: 'github-secret-accessor@actions-oidc-test.iam.gserviceaccount.com'
- id_token_audience: 'my-aud'
+
+ - id: 'setup-gcloud'
+ name: 'setup-gcloud'
+ uses: 'google-github-actions/setup-gcloud@master'
+
+ - id: 'gcloud'
+ name: 'gcloud'
+ shell: 'bash'
+ run: |-
+ gcloud secrets versions access "latest" --secret "my-secret"
+
+ - id: 'auth-access-token'
+ name: 'auth-access-token'
+ uses: './'
+ with:
+ workload_identity_provider: 'projects/469401941463/locations/global/workloadIdentityPools/github-actions/providers/google-github-actions'
+ service_account: 'github-secret-accessor@actions-oidc-test.iam.gserviceaccount.com'
+ token_format: 'access_token'
+
+ - id: 'access-token'
+ name: 'access-token'
+ shell: 'bash'
+ run: |-
+ curl https://secretmanager.googleapis.com/v1/projects/${{ steps.auth-access-token.outputs.project_id }}/secrets/my-secret/versions/latest:access \
+ --silent \
+ --show-error \
+ --fail \
+ --header "Authorization: Bearer ${{ steps.auth-access-token.outputs.access_token }}"
+
+ - id: 'auth-id-token'
+ name: 'auth-id-token'
+ uses: './'
+ with:
+ workload_identity_provider: 'projects/469401941463/locations/global/workloadIdentityPools/github-actions/providers/google-github-actions'
+ service_account: 'github-secret-accessor@actions-oidc-test.iam.gserviceaccount.com'
+ token_format: 'id_token'
+ id_token_audience: 'https://secretmanager.googleapis.com/'
id_token_include_email: true
diff --git a/.prettierrc.js b/.prettierrc.js
index 13a7041..516e613 100644
--- a/.prettierrc.js
+++ b/.prettierrc.js
@@ -2,7 +2,6 @@ module.exports = {
arrowParens: 'always',
bracketSpacing: true,
endOfLine: 'auto',
- jsxBracketSameLine: true,
jsxSingleQuote: true,
printWidth: 100,
quoteProps: 'consistent',
diff --git a/README.md b/README.md
index 45048c3..4ce74a7 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,15 @@
# auth
-This GitHub Action exchanges a GitHub Actions OIDC token into a Google Cloud
-access token using [Workload Identity Federation][wif]. This obviates the need
+This GitHub Action establishes authentication to Google Cloud. It supports
+traditional authentication via a Google Cloud Service Account Key JSON and
+authentication via [Workload Identity Federation][wif].
+
+Workload Identity Federation is the recommended approach as it obviates the need
to export a long-lived Google Cloud service account key and establishes a trust
delegation relationship between a particular GitHub Actions workflow invocation
and permissions on Google Cloud.
-#### Previously
+#### With Service Account Key JSON
1. Create a Google Cloud service account and grant IAM permissions
1. Export the long-lived JSON service account key
@@ -22,8 +25,12 @@ and permissions on Google Cloud.
## Prerequisites
-- This action requires you to create and configure a Google Cloud Workload
- Identity Provider. See [#setup](#setup) for instructions.
+- For authenticating via Google Cloud Service Account Keys, you must create an
+ export a Google Cloud Service Account Key in JSON format.
+
+- For authenticating via Workload Identity Federation, you must create and
+ configure a Google Cloud Workload Identity Provider. See [setup](#setup)
+ for instructions.
## Usage
@@ -62,17 +69,21 @@ See [Examples](#examples) for more examples.
## Inputs
-- `workload_identity_provider`: (Required) The full identifier of the Workload
- Identity Provider, including the project number, pool name, and provider
- name. This must be the full identifier which includes all parts, for
- example:
+### Authenticating via Workload Identity Federation
+
+The following inputs are for _authenticating_ to Google Cloud via Workload
+Identity Federation.
+
+- `workload_identity_provider`: (Required) The full identifier of the Workload Identity
+ Provider, including the project number, pool name, and provider name. If
+ provided, this must be the full identifier which includes all parts:
```text
projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider
```
-- `service_account`: (Required) Email address or unique identifier of the
- Google Cloud service account for which to generate credentials. For example:
+- `service_account`: (Required) Email address or unique identifier of the Google Cloud
+ service account for which to generate credentials. For example:
```text
my-service-account@my-project.iam.gserviceaccount.com
@@ -81,30 +92,29 @@ See [Examples](#examples) for more examples.
- `audience`: (Optional) The value for the audience (`aud`) parameter in the
generated GitHub Actions OIDC token. This value defaults to the value of
`workload_identity_provider`, which is also the default value Google Cloud
- expects for the audience parameter on the token.
+ expects for the audience parameter on the token. We do not recommend
+ changing this value.
-- `create_credentials_file`: (Optional) If true, the action will securely
- generate a credentials file which can be used for authentication via gcloud
- and Google Cloud SDKs. The default is false.
+### Authenticating via Service Account Key JSON
-- `activate_credentials_file`: (Optional) If true and
- "create_credentials_file" is also true, this will set the
- `GOOGLE_APPLICATION_CREDENTIALS` environment variable to the path to the
- credentials file, which gcloud and Google Cloud SDKs automatically consume.
- The default value is true.
+The following inputs are for _authenticating_ to Google Cloud via a Service
+Account Key JSON. **We recommend using Workload Identity Federation instead as
+exporting a long-lived Service Account Key JSON credential poses a security
+risk.**
-- `token_format`: (Optional) Output format for the generated authentication
- token.
+- `credentials_json`: (Required) The Google Cloud JSON service account key to
+ use for authentication. To generate access tokens or ID tokens using this
+ service account, you must grant the underlying service account
+ `roles/iam.serviceAccountTokenCreator` permissions on itself.
- - For OAuth 2.0 access tokens, specify "access_token".
- - For OIDC tokens, specify "id_token".
- - To skip token generation, omit or set to the empty string "".
+### Generating OAuth 2.0 access tokens
- The default value is "" (skip token creation).
+The following inputs are for _generating_ OAuth 2.0 access tokens for
+authenticating to Google Cloud as an output for use in future steps in the
+workflow.
-- `delegates`: (Optional) List of additional service account emails or unique
- identities to use for impersonation in the chain. By default there are no
- delegates.
+- `token_format`: This value must be `"access_token"` to generate OAuth 2.0
+ access tokens. To skip token generation, omit or set to the empty string "".
- `access_token_lifetime`: (Optional) Desired lifetime duration of the access
token, in seconds. This must be specified as the number of seconds with a
@@ -118,18 +128,43 @@ See [Examples](#examples) for more examples.
https://www.googleapis.com/auth/cloud-platform
```
-- `id_token_audience`: (Required\*) The audience for the generated ID Token.
- This option is required when "token_format" is "id_token", but otherwise can
- be omitted.
+### Generating ID tokens
+
+The following inputs are for _generating_ ID tokens for authenticating to Google
+Cloud as an output for use in future steps in the workflow.
+
+- `token_format`: This value must be `"id_token"` to generate ID tokens. To
+ skip token generation, omit or set to the empty string "".
+
+- `id_token_audience`: (Required) The audience for the generated ID Token.
- `id_token_include_email`: (Optional) Optional parameter of whether to
include the service account email in the generated token. If true, the token
will contain "email" and "email_verified" claims. This is only valid when
"token_format" is "id_token". The default value is false.
+### Other inputs
+
+The following inputs are for controlling the behavior of this GitHub Actions,
+regardless of the authentication mechanism.
+
+- `project_id`: (Optional) Custom project ID to use for authentication and
+ exporting into other steps. If unspecified, the project ID will be extracted
+ from the Workload Identity Provider or the Service Account Key JSON.
+
+- `create_credentials_file`: (Optional) If true, the action will securely
+ generate a credentials file which can be used for authentication via gcloud
+ and Google Cloud SDKs in other steps in the workflow. The default is true.
+
+- `delegates`: (Optional) List of additional service account emails or unique
+ identities to use for impersonation in the chain. By default there are no
+ delegates.
+
## Outputs
+- `project_id`: Provided or extracted value for the Google Cloud project ID.
+
- `credentials_file_path`: Path on the local filesystem where the generated
credentials file resides. This is only available if
"create_credentials_file" was set to true.
@@ -145,10 +180,60 @@ See [Examples](#examples) for more examples.
## Examples
-#### Cloud SDK (gcloud)
+### Authenticating via Workload Identity Federation
+
+This example demonstrates authenticating via Workload Identity Federation. For
+more information on how to setup and configure Workload Identity Federation, see
+[#setup](#setup).
+
+```yaml
+jobs:
+ job_id:
+ # ...
+
+ # Add "id-token" with the intended permissions.
+ permissions:
+ contents: 'read'
+ id-token: 'write'
+
+ steps:
+ - id: 'auth'
+ name: 'Authenticate to Google Cloud'
+ uses: `google-github-actions/auth@v0.3.1'
+ with:
+ workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
+ service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
+```
+
+### Authenticating via Service Account Key JSON
+
+This example demonstrates authenticating via a Google Cloud Service Account Key
+JSON. **We recommend using Workload Identity Federation instead as exporting a
+long-lived Service Account Key JSON credential poses a security risk.**
+
+This example assumes you have created a GitHub Secret named 'GOOGLE_CREDENTIALS'
+with the contents being an export Google Cloud Service Account Key JSON. See
+[Creating and managing Google Cloud Service Account
+Keys](https://cloud.google.com/iam/docs/creating-managing-service-account-keys)
+for more information.
+
+```yaml
+jobs:
+ job_id:
+ # ...
+
+ steps:
+ - id: 'auth'
+ name: 'Authenticate to Google Cloud'
+ uses: `google-github-actions/auth@v0.3.1'
+ with:
+ credentials_json: '${{ secrets.GOOGLE_CREDENTIALS }}'
+```
+
+### Configuring gcloud
This example demonstrates using this GitHub Action to configure authentication
-for the `gcloud` CLI tool. Note this does **not** work for the `gsutil` tool.
+for the `gcloud` CLI tool. Note this does **NOT** work for the `gsutil` tool.
```yaml
jobs:
@@ -171,7 +256,6 @@ jobs:
name: 'Authenticate to Google Cloud'
uses: 'google-github-actions/auth@v0.3.1'
with:
- create_credentials_file: 'true'
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
@@ -187,7 +271,7 @@ jobs:
gcloud secrets versions access "latest" --secret "my-secret"
```
-#### Access Token (OAuth 2.0)
+### Generating an OAuth 2.0 Access Token
This example demonstrates using this GitHub Action to generate an OAuth 2.0
Access Token for authenticating to Google Cloud. Most Google Cloud APIs accept
@@ -196,6 +280,9 @@ this access token as authentication.
The default lifetime is 1 hour, but you can request up to 12 hours if you set
the [`constraints/iam.allowServiceAccountCredentialLifetimeExtension` organization policy](https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints).
+Note: If you authenticate via `credentials_json`, the service account must have
+`roles/iam.serviceAccountTokenCreator` on itself.
+
```yaml
jobs:
job_id:
@@ -225,12 +312,15 @@ jobs:
--header "Authorization: Bearer ${{ steps.auth.outputs.access_token }}"
```
-#### ID Token (JWT)
+### Generating an ID Token (JWT)
This example demonstrates using this GitHub Action to generate a Google Cloud ID
Token for authenticating to Google Cloud. This is most commonly used when
invoking a Cloud Run service.
+Note: If you authenticate via `credentials_json`, the service account must have
+`roles/iam.serviceAccountTokenCreator` on itself.
+
```yaml
jobs:
job_id:
@@ -262,7 +352,9 @@ jobs:
```
-## Setup
+
+
+## Setting up Workload Identity Federation
To exchange a GitHub Actions OIDC token for a Google Cloud access token, you
must create and configure a Workload Identity Provider. These instructions use
diff --git a/action.yml b/action.yml
index 389f02f..67c3f2e 100644
--- a/action.yml
+++ b/action.yml
@@ -15,42 +15,48 @@
name: 'Authenticate to Google Cloud'
author: 'Google LLC'
description: |-
- Generate credentials to authenticate to Google Cloud from GitHub Actions using
- an OIDC token and Workload Identity Federation.
+ Authenticate to Google Cloud from GitHub Actions via Workload Identity
+ Federation and GitHub Actions OIDC tokens or via exported Google Cloud service
+ account keys.
inputs:
+ project_id:
+ description: |-
+ ID of the default project to use for future API calls and invocations. If
+ unspecified, this action will attempt to extract the value from other
+ inputs such as "service_account" or "credentials_json".
+ required: false
workload_identity_provider:
description: |-
The full identifier of the Workload Identity Provider, including the
- project number, pool name, and provider name. This must be the full
- identifier which includes all parts, for example:
+ project number, pool name, and provider name. If provided, this must be
+ the full identifier which includes all parts, for example:
"projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider".
- required: true
+ This is mutually exclusive with "credentials_json".
+ required: false
service_account:
description: |-
Email address or unique identifier of the Google Cloud service account for
- which to generate credentials.
- required: true
+ which to generate credentials. This is required if
+ "workload_identity_provider" is specified.
+ required: false
audience:
description: |-
The value for the audience (aud) parameter in GitHub's generated OIDC
- token. This value defaults to the value of workload_identity_provider,
+ token. This value defaults to the value of "workload_identity_provider",
which is also the default value Google Cloud expects for the audience
parameter on the token.
default: ''
required: false
+ credentials_json:
+ description: |-
+ The Google Cloud JSON service account key to use for authentication. This
+ is mutually exclusive with "workload_identity_provider".
+ required: false
create_credentials_file:
description: |-
If true, the action will securely generate a credentials file which can be
used for authentication via gcloud and Google Cloud SDKs.
- default: false
- required: false
- activate_credentials_file:
- description: |-
- If true and create_credentials_file is also true, this will set the
- GOOGLE_APPLICATION_CREDENTIALS environment variable to the path to the
- credentials file, which gcloud and Google Cloud SDKs automatically
- consume.
default: true
required: false
token_format:
@@ -99,6 +105,9 @@ inputs:
required: false
outputs:
+ project_id:
+ description: |-
+ Provided or extracted value for the Google Cloud project ID.
credentials_file_path:
description: |-
Path on the local filesystem where the generated credentials file resides.
diff --git a/dist/index.js b/dist/index.js
index 9734422..9a533ea 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -194,7 +194,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(__webpack_require__(470));
-const workload_identity_1 = __webpack_require__(313);
+const workload_identity_client_1 = __webpack_require__(911);
+const credentials_json_client_1 = __webpack_require__(627);
+const base_1 = __webpack_require__(843);
const utils_1 = __webpack_require__(163);
/**
* Executes the main action, documented inline.
@@ -203,21 +205,44 @@ function run() {
return __awaiter(this, void 0, void 0, function* () {
try {
// Load configuration.
- const workloadIdentityProvider = core.getInput('workload_identity_provider', {
- required: true,
- });
- const serviceAccount = core.getInput('service_account', { required: true });
- // audience will default to the WIF provider ID when used with WIF
- const audience = core.getInput('audience');
+ const projectID = core.getInput('project_id');
+ const workloadIdentityProvider = core.getInput('workload_identity_provider');
+ const serviceAccount = core.getInput('service_account');
+ const audience = core.getInput('audience') || `https://iam.googleapis.com/${workloadIdentityProvider}`;
+ const credentialsJSON = core.getInput('credentials_json');
const createCredentialsFile = core.getBooleanInput('create_credentials_file');
- const activateCredentialsFile = core.getBooleanInput('activate_credentials_file');
const tokenFormat = core.getInput('token_format');
const delegates = (0, utils_1.explodeStrings)(core.getInput('delegates'));
- const client = new workload_identity_1.WIFClient({
- providerID: workloadIdentityProvider,
- serviceAccount: serviceAccount,
- audience: audience,
- });
+ // Ensure exactly one of workload_identity_provider and credentials_json was
+ // provided.
+ if ((!workloadIdentityProvider && !credentialsJSON) ||
+ (workloadIdentityProvider && credentialsJSON)) {
+ throw new Error('The GitHub Action workflow must specify exactly one of ' +
+ '"workload_identity_provider" or "credentials_json"!');
+ }
+ // Ensure a service_account was provided if using WIF.
+ if (workloadIdentityProvider && !serviceAccount) {
+ throw new Error('The GitHub Action workflow must specify a "service_account" to ' +
+ 'impersonate when using "workload_identity_provider"!');
+ }
+ // Instantiate the correct client based on the provided input parameters.
+ let client;
+ if (workloadIdentityProvider) {
+ const token = yield core.getIDToken(audience);
+ client = new workload_identity_client_1.WorkloadIdentityClient({
+ projectID: projectID,
+ providerID: workloadIdentityProvider,
+ serviceAccount: serviceAccount,
+ token: token,
+ audience: audience,
+ });
+ }
+ else {
+ client = new credentials_json_client_1.CredentialsJSONClient({
+ projectID: projectID,
+ credentialsJSON: credentialsJSON,
+ });
+ }
// Always write the credentials file first, before trying to generate
// tokens. This will ensure the file is written even if token generation
// fails, which means continue-on-error actions will still have the file
@@ -227,16 +252,19 @@ function run() {
if (!runnerTempDir) {
throw new Error('$RUNNER_TEMP is not set');
}
- const { credentialsPath, envVars } = yield client.createCredentialsFile(runnerTempDir);
+ const credentialsPath = yield client.createCredentialsFile(runnerTempDir);
core.setOutput('credentials_file_path', credentialsPath);
- // Also set the magic environment variable for gcloud and SDKs if
- // requested.
- if (activateCredentialsFile && envVars) {
- for (const [k, v] of envVars) {
- core.exportVariable(k, v);
- }
- }
+ core.exportVariable('CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE', credentialsPath);
+ core.exportVariable('GOOGLE_APPLICATION_CREDENTIALS', credentialsPath);
}
+ // Set the project ID environment variables to the computed values.
+ const computedProjectID = yield client.getProjectID();
+ core.setOutput('project_id', computedProjectID);
+ core.exportVariable('CLOUDSDK_PROJECT', computedProjectID);
+ core.exportVariable('CLOUDSDK_CORE_PROJECT', computedProjectID);
+ core.exportVariable('GCP_PROJECT', computedProjectID);
+ core.exportVariable('GCLOUD_PROJECT', computedProjectID);
+ core.exportVariable('GOOGLE_CLOUD_PROJECT', computedProjectID);
switch (tokenFormat) {
case '': {
break;
@@ -247,7 +275,9 @@ function run() {
case 'access_token': {
const accessTokenLifetime = core.getInput('access_token_lifetime');
const accessTokenScopes = (0, utils_1.explodeStrings)(core.getInput('access_token_scopes'));
- const { accessToken, expiration } = yield client.getAccessToken({
+ const serviceAccount = yield client.getServiceAccount();
+ const authToken = yield client.getAuthToken();
+ const { accessToken, expiration } = yield base_1.BaseClient.googleAccessToken(authToken, {
serviceAccount,
delegates,
scopes: accessTokenScopes,
@@ -261,7 +291,9 @@ function run() {
case 'id_token': {
const idTokenAudience = core.getInput('id_token_audience', { required: true });
const idTokenIncludeEmail = core.getBooleanInput('id_token_include_email');
- const { token } = yield client.getIDToken({
+ const serviceAccount = yield client.getServiceAccount();
+ const authToken = yield client.getAuthToken();
+ const { token } = yield base_1.BaseClient.googleIDToken(authToken, {
serviceAccount,
audience: idTokenAudience,
delegates,
@@ -272,7 +304,7 @@ function run() {
break;
}
default: {
- throw new Error(`unknown token format "${tokenFormat}"`);
+ throw new Error(`Unknown token format "${tokenFormat}"`);
}
}
}
@@ -576,19 +608,19 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.explodeStrings = exports.writeCredFile = void 0;
+exports.fromBase64 = exports.toBase64 = exports.explodeStrings = exports.writeSecureFile = void 0;
const fs_1 = __webpack_require__(747);
const crypto_1 = __importDefault(__webpack_require__(417));
const path_1 = __importDefault(__webpack_require__(622));
/**
- * writeCredFile writes a file to disk in a given directory with a
+ * writeSecureFile writes a file to disk in a given directory with a
* random name.
*
* @param outputDir Directory to create random file in.
* @param data Data to write to file.
* @returns Path to written file.
*/
-function writeCredFile(outputDir, data) {
+function writeSecureFile(outputDir, data) {
return __awaiter(this, void 0, void 0, function* () {
// Generate a random filename to store the credential. 12 bytes is 24
// characters in hex. It's not the ideal entropy, but we have to be under
@@ -602,7 +634,7 @@ function writeCredFile(outputDir, data) {
return pth;
});
}
-exports.writeCredFile = writeCredFile;
+exports.writeSecureFile = writeSecureFile;
/**
* Converts a multi-line or comma-separated collection of strings into an array
* of trimmed strings.
@@ -623,6 +655,28 @@ function explodeStrings(input) {
return list;
}
exports.explodeStrings = explodeStrings;
+/**
+ * toBase64 base64 URL encodes the result.
+ */
+function toBase64(s) {
+ return Buffer.from(s)
+ .toString('base64')
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_')
+ .replace(/=+$/, '');
+}
+exports.toBase64 = toBase64;
+/**
+ * fromBase64 base64 decodes the result, taking into account URL and standard
+ * encoding with and without padding.
+ */
+function fromBase64(s) {
+ const str = s.replace(/-/g, '+').replace(/_/g, '/');
+ while (str.length % 4)
+ s += '=';
+ return Buffer.from(str, 'base64').toString('utf8');
+}
+exports.fromBase64 = fromBase64;
/***/ }),
@@ -698,174 +752,6 @@ class PersonalAccessTokenCredentialHandler {
exports.PersonalAccessTokenCredentialHandler = PersonalAccessTokenCredentialHandler;
-/***/ }),
-
-/***/ 313:
-/***/ (function(__unusedmodule, exports, __webpack_require__) {
-
-"use strict";
-
-var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
- if (k2 === undefined) k2 = k;
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
-}) : (function(o, m, k, k2) {
- if (k2 === undefined) k2 = k;
- o[k2] = m[k];
-}));
-var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
- Object.defineProperty(o, "default", { enumerable: true, value: v });
-}) : function(o, v) {
- o["default"] = v;
-});
-var __importStar = (this && this.__importStar) || function (mod) {
- if (mod && mod.__esModule) return mod;
- var result = {};
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
- __setModuleDefault(result, mod);
- return result;
-};
-var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
-};
-Object.defineProperty(exports, "__esModule", { value: true });
-exports.WIFClient = void 0;
-const url_1 = __webpack_require__(835);
-const core = __importStar(__webpack_require__(470));
-const utils_1 = __webpack_require__(163);
-const base_1 = __webpack_require__(843);
-class WIFClient {
- constructor(opts) {
- this.providerID = opts.providerID;
- this.serviceAccount = opts.serviceAccount;
- this.audience = opts.audience ? opts.audience : `https://iam.googleapis.com/${this.providerID}`;
- }
- /**
- * googleFederatedToken generates a Google Cloud federated token using the
- * provided OIDC token and Workload Identity Provider.
- */
- static googleFederatedToken({ providerID, token, }) {
- return __awaiter(this, void 0, void 0, function* () {
- const stsURL = new url_1.URL('https://sts.googleapis.com/v1/token');
- const data = {
- audience: '//iam.googleapis.com/' + providerID,
- grantType: 'urn:ietf:params:oauth:grant-type:token-exchange',
- requestedTokenType: 'urn:ietf:params:oauth:token-type:access_token',
- scope: 'https://www.googleapis.com/auth/cloud-platform',
- subjectTokenType: 'urn:ietf:params:oauth:token-type:jwt',
- subjectToken: token,
- };
- const opts = {
- hostname: stsURL.hostname,
- port: stsURL.port,
- path: stsURL.pathname + stsURL.search,
- method: 'POST',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- },
- };
- try {
- const resp = yield base_1.BaseClient.request(opts, JSON.stringify(data));
- const parsed = JSON.parse(resp);
- return parsed['access_token'];
- }
- catch (err) {
- throw new Error(`failed to generate Google Cloud federated token for ${providerID}: ${err}`);
- }
- });
- }
- /**
- * getFederatedToken generates a Google Cloud federated token using the
- * GitHub OIDC token.
- */
- getFederatedToken() {
- return __awaiter(this, void 0, void 0, function* () {
- // Get the GitHub OIDC token.
- const githubOIDCToken = yield core.getIDToken(this.audience);
- // Exchange the GitHub OIDC token for a Google Federated Token.
- const googleFederatedToken = yield WIFClient.googleFederatedToken({
- providerID: this.providerID,
- token: githubOIDCToken,
- });
- core.setSecret(googleFederatedToken);
- return googleFederatedToken;
- });
- }
- /**
- * getAccessToken generates a Google Cloud access token for the provided
- * service account email or unique id.
- */
- getAccessToken(opts) {
- return __awaiter(this, void 0, void 0, function* () {
- const googleFederatedToken = yield this.getFederatedToken();
- return yield base_1.BaseClient.googleAccessToken(googleFederatedToken, opts);
- });
- }
- /**
- * getIDToken generates a Google Cloud ID token for the provided
- * service account email or unique id.
- */
- getIDToken(tokenParams) {
- return __awaiter(this, void 0, void 0, function* () {
- const googleFederatedToken = yield this.getFederatedToken();
- return yield base_1.BaseClient.googleIDToken(googleFederatedToken, tokenParams);
- });
- }
- /**
- * createCredentialsFile creates a Google Cloud credentials file that can be
- * set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries.
- */
- createCredentialsFile(outputDir) {
- return __awaiter(this, void 0, void 0, function* () {
- // Extract the request token and request URL from the environment. These
- // are only set when an id-token is requested and the submitter has
- // collaborator permissions.
- const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
- const requestURLRaw = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
- if (!requestToken || !requestURLRaw) {
- throw new Error('GitHub Actions did not inject $ACTIONS_ID_TOKEN_REQUEST_TOKEN or ' +
- '$ACTIONS_ID_TOKEN_REQUEST_URL into this job. This most likely ' +
- 'means the GitHub Actions workflow permissions are incorrect, or ' +
- 'this job is being run from a fork. For more information, please ' +
- 'see the GitHub documentation at https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token');
- }
- const requestURL = new url_1.URL(requestURLRaw);
- // Append the audience value to the request.
- const params = requestURL.searchParams;
- params.set('audience', this.audience);
- requestURL.search = params.toString();
- const data = {
- type: 'external_account',
- audience: `//iam.googleapis.com/${this.providerID}`,
- 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/${this.serviceAccount}:generateAccessToken`,
- credential_source: {
- url: requestURL,
- headers: {
- Authorization: `Bearer ${requestToken}`,
- },
- format: {
- type: 'json',
- subject_token_field_name: 'value',
- },
- },
- };
- const credentialsPath = yield (0, utils_1.writeCredFile)(outputDir, JSON.stringify(data));
- const envVars = new Map([['GOOGLE_APPLICATION_CREDENTIALS', credentialsPath]]);
- return { credentialsPath, envVars };
- });
- }
-}
-exports.WIFClient = WIFClient;
-
-
/***/ }),
/***/ 357:
@@ -1872,6 +1758,130 @@ module.exports = require("events");
module.exports = require("path");
+/***/ }),
+
+/***/ 627:
+/***/ (function(__unusedmodule, exports, __webpack_require__) {
+
+"use strict";
+
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
+ if (kind === "m") throw new TypeError("Private method is not writable");
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
+};
+var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
+};
+var _CredentialsJSONClient_projectID, _CredentialsJSONClient_credentials;
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.CredentialsJSONClient = void 0;
+const crypto_1 = __webpack_require__(417);
+const utils_1 = __webpack_require__(163);
+class CredentialsJSONClient {
+ constructor(opts) {
+ _CredentialsJSONClient_projectID.set(this, void 0);
+ _CredentialsJSONClient_credentials.set(this, void 0);
+ __classPrivateFieldSet(this, _CredentialsJSONClient_credentials, this.parseServiceAccountKeyJSON(opts.credentialsJSON), "f");
+ __classPrivateFieldSet(this, _CredentialsJSONClient_projectID, opts.projectID || __classPrivateFieldGet(this, _CredentialsJSONClient_credentials, "f")['project_id'], "f");
+ }
+ /**
+ * parseServiceAccountKeyJSON attempts to parse the given string as a service
+ * account key JSON. It handles if the string is base64-encoded.
+ */
+ parseServiceAccountKeyJSON(str) {
+ if (!str) {
+ return {};
+ }
+ str = str.trim();
+ // If the string doesn't start with a JSON object character, it is probably
+ // base64-encoded.
+ if (!str.startsWith('{')) {
+ str = (0, utils_1.fromBase64)(str);
+ }
+ try {
+ return JSON.parse(str);
+ }
+ catch (e) {
+ throw new SyntaxError(`Failed to parse credentials as JSON: ${e}`);
+ }
+ }
+ /**
+ * getAuthToken generates a token capable of calling the iamcredentials API.
+ */
+ getAuthToken() {
+ return __awaiter(this, void 0, void 0, function* () {
+ const header = {
+ alg: 'RS256',
+ typ: 'JWT',
+ kid: __classPrivateFieldGet(this, _CredentialsJSONClient_credentials, "f")['private_key_id'],
+ };
+ const now = Math.floor(new Date().getTime() / 1000);
+ const body = {
+ iss: __classPrivateFieldGet(this, _CredentialsJSONClient_credentials, "f")['client_email'],
+ sub: __classPrivateFieldGet(this, _CredentialsJSONClient_credentials, "f")['client_email'],
+ aud: 'https://iamcredentials.googleapis.com/',
+ iat: now,
+ exp: now + 3599,
+ };
+ const message = (0, utils_1.toBase64)(JSON.stringify(header)) + '.' + (0, utils_1.toBase64)(JSON.stringify(body));
+ try {
+ const signer = (0, crypto_1.createSign)('RSA-SHA256');
+ signer.write(message);
+ signer.end();
+ const signature = signer.sign(__classPrivateFieldGet(this, _CredentialsJSONClient_credentials, "f")['private_key']);
+ return message + '.' + (0, utils_1.toBase64)(signature);
+ }
+ catch (e) {
+ throw new Error(`Failed to sign auth token: ${e}`);
+ }
+ });
+ }
+ /**
+ * getProjectID returns the project ID. If an override was given, the override
+ * is returned. Otherwise, this will be the project ID that was extracted from
+ * the service account key JSON.
+ */
+ getProjectID() {
+ return __awaiter(this, void 0, void 0, function* () {
+ return __classPrivateFieldGet(this, _CredentialsJSONClient_projectID, "f");
+ });
+ }
+ /**
+ * getServiceAccount returns the service account email for the authentication,
+ * extracted from the Service Account Key JSON.
+ */
+ getServiceAccount() {
+ return __awaiter(this, void 0, void 0, function* () {
+ return __classPrivateFieldGet(this, _CredentialsJSONClient_credentials, "f")['client_email'];
+ });
+ }
+ /**
+ * createCredentialsFile creates a Google Cloud credentials file that can be
+ * set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries.
+ */
+ createCredentialsFile(outputDir) {
+ return __awaiter(this, void 0, void 0, function* () {
+ return yield (0, utils_1.writeSecureFile)(outputDir, JSON.stringify(__classPrivateFieldGet(this, _CredentialsJSONClient_credentials, "f")));
+ });
+ }
+}
+exports.CredentialsJSONClient = CredentialsJSONClient;
+_CredentialsJSONClient_projectID = new WeakMap(), _CredentialsJSONClient_credentials = new WeakMap();
+
+
/***/ }),
/***/ 631:
@@ -2113,8 +2123,8 @@ class BaseClient {
expiration: parsed['expireTime'],
};
}
- catch (err) {
- throw new Error(`failed to generate Google Cloud access token for ${serviceAccount}: ${err}`);
+ catch (e) {
+ throw new Error(`Failed to generate Google Cloud access token for ${serviceAccount}: ${e}`);
}
});
}
@@ -2122,6 +2132,171 @@ class BaseClient {
exports.BaseClient = BaseClient;
+/***/ }),
+
+/***/ 911:
+/***/ (function(__unusedmodule, exports, __webpack_require__) {
+
+"use strict";
+
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
+ if (kind === "m") throw new TypeError("Private method is not writable");
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
+ return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
+};
+var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
+ if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
+ if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
+ return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
+};
+var _WorkloadIdentityClient_projectID, _WorkloadIdentityClient_providerID, _WorkloadIdentityClient_serviceAccount, _WorkloadIdentityClient_token, _WorkloadIdentityClient_audience;
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.WorkloadIdentityClient = void 0;
+const url_1 = __webpack_require__(835);
+const utils_1 = __webpack_require__(163);
+const base_1 = __webpack_require__(843);
+class WorkloadIdentityClient {
+ constructor(opts) {
+ _WorkloadIdentityClient_projectID.set(this, void 0);
+ _WorkloadIdentityClient_providerID.set(this, void 0);
+ _WorkloadIdentityClient_serviceAccount.set(this, void 0);
+ _WorkloadIdentityClient_token.set(this, void 0);
+ _WorkloadIdentityClient_audience.set(this, void 0);
+ __classPrivateFieldSet(this, _WorkloadIdentityClient_providerID, opts.providerID, "f");
+ __classPrivateFieldSet(this, _WorkloadIdentityClient_serviceAccount, opts.serviceAccount, "f");
+ __classPrivateFieldSet(this, _WorkloadIdentityClient_token, opts.token, "f");
+ __classPrivateFieldSet(this, _WorkloadIdentityClient_audience, opts.audience, "f");
+ __classPrivateFieldSet(this, _WorkloadIdentityClient_projectID, opts.projectID || this.extractProjectIDFromServiceAccountEmail(__classPrivateFieldGet(this, _WorkloadIdentityClient_serviceAccount, "f")), "f");
+ }
+ /**
+ * extractProjectIDFromServiceAccountEmail extracts the project ID from the
+ * service account email address.
+ */
+ extractProjectIDFromServiceAccountEmail(str) {
+ if (!str) {
+ return '';
+ }
+ const [, dn] = str.split('@', 2);
+ if (!str.endsWith('.iam.gserviceaccount.com')) {
+ throw new Error(`Service account email ${str} is not of the form ` +
+ `"[name]@[project].iam.gserviceaccount.com. You must manually ` +
+ `specify the "project_id" parameter in your GitHub Actions workflow.`);
+ }
+ const [project] = dn.split('.', 2);
+ return project;
+ }
+ /**
+ * getAuthToken generates a Google Cloud federated token using the provided OIDC
+ * token and Workload Identity Provider.
+ */
+ getAuthToken() {
+ return __awaiter(this, void 0, void 0, function* () {
+ const stsURL = new url_1.URL('https://sts.googleapis.com/v1/token');
+ const data = {
+ audience: '//iam.googleapis.com/' + __classPrivateFieldGet(this, _WorkloadIdentityClient_providerID, "f"),
+ grantType: 'urn:ietf:params:oauth:grant-type:token-exchange',
+ requestedTokenType: 'urn:ietf:params:oauth:token-type:access_token',
+ scope: 'https://www.googleapis.com/auth/cloud-platform',
+ subjectTokenType: 'urn:ietf:params:oauth:token-type:jwt',
+ subjectToken: __classPrivateFieldGet(this, _WorkloadIdentityClient_token, "f"),
+ };
+ const opts = {
+ hostname: stsURL.hostname,
+ port: stsURL.port,
+ path: stsURL.pathname + stsURL.search,
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ };
+ try {
+ const resp = yield base_1.BaseClient.request(opts, JSON.stringify(data));
+ const parsed = JSON.parse(resp);
+ return parsed['access_token'];
+ }
+ catch (err) {
+ throw new Error(`Failed to generate Google Cloud federated token for ${__classPrivateFieldGet(this, _WorkloadIdentityClient_providerID, "f")}: ${err}`);
+ }
+ });
+ }
+ /**
+ * getProjectID returns the project ID. If an override was given, the override
+ * is returned. Otherwise, this will be the project ID that was extracted from
+ * the service account key JSON.
+ */
+ getProjectID() {
+ return __awaiter(this, void 0, void 0, function* () {
+ return __classPrivateFieldGet(this, _WorkloadIdentityClient_projectID, "f");
+ });
+ }
+ /**
+ * getServiceAccount returns the service account email for the authentication,
+ * extracted from the input parameter.
+ */
+ getServiceAccount() {
+ return __awaiter(this, void 0, void 0, function* () {
+ return __classPrivateFieldGet(this, _WorkloadIdentityClient_serviceAccount, "f");
+ });
+ }
+ /**
+ * createCredentialsFile creates a Google Cloud credentials file that can be
+ * set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries.
+ */
+ createCredentialsFile(outputDir) {
+ return __awaiter(this, void 0, void 0, function* () {
+ // Extract the request token and request URL from the environment. These
+ // are only set when an id-token is requested and the submitter has
+ // collaborator permissions.
+ const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
+ const requestURLRaw = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
+ if (!requestToken || !requestURLRaw) {
+ throw new Error('GitHub Actions did not inject $ACTIONS_ID_TOKEN_REQUEST_TOKEN or ' +
+ '$ACTIONS_ID_TOKEN_REQUEST_URL into this job. This most likely ' +
+ 'means the GitHub Actions workflow permissions are incorrect, or ' +
+ 'this job is being run from a fork. For more information, please ' +
+ 'see the GitHub documentation at https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token');
+ }
+ const requestURL = new url_1.URL(requestURLRaw);
+ // Append the audience value to the request.
+ const params = requestURL.searchParams;
+ params.set('audience', __classPrivateFieldGet(this, _WorkloadIdentityClient_audience, "f"));
+ requestURL.search = params.toString();
+ const data = {
+ type: 'external_account',
+ audience: `//iam.googleapis.com/${__classPrivateFieldGet(this, _WorkloadIdentityClient_providerID, "f")}`,
+ 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/${__classPrivateFieldGet(this, _WorkloadIdentityClient_serviceAccount, "f")}:generateAccessToken`,
+ credential_source: {
+ url: requestURL,
+ headers: {
+ Authorization: `Bearer ${requestToken}`,
+ },
+ format: {
+ type: 'json',
+ subject_token_field_name: 'value',
+ },
+ },
+ };
+ return yield (0, utils_1.writeSecureFile)(outputDir, JSON.stringify(data));
+ });
+ }
+}
+exports.WorkloadIdentityClient = WorkloadIdentityClient;
+_WorkloadIdentityClient_projectID = new WeakMap(), _WorkloadIdentityClient_providerID = new WeakMap(), _WorkloadIdentityClient_serviceAccount = new WeakMap(), _WorkloadIdentityClient_token = new WeakMap(), _WorkloadIdentityClient_audience = new WeakMap();
+
+
/***/ }),
/***/ 950:
diff --git a/package-lock.json b/package-lock.json
index 660edba..45b4f05 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -64,12 +64,12 @@
}
},
"node_modules/@babel/highlight": {
- "version": "7.14.5",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
- "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
+ "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
"dev": true,
"dependencies": {
- "@babel/helper-validator-identifier": "^7.14.5",
+ "@babel/helper-validator-identifier": "^7.15.7",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
},
@@ -158,9 +158,9 @@
}
},
"node_modules/@cspotcode/source-map-support": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz",
- "integrity": "sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==",
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
+ "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
"dev": true,
"dependencies": {
"@cspotcode/source-map-consumer": "0.8.0"
@@ -213,9 +213,9 @@
}
},
"node_modules/@humanwhocodes/object-schema": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz",
- "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true
},
"node_modules/@nodelib/fs.scandir": {
@@ -296,19 +296,19 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "16.10.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.1.tgz",
- "integrity": "sha512-4/Z9DMPKFexZj/Gn3LylFgamNKHm4K3QDi0gz9B26Uk0c8izYf97B5fxfpspMNkWlFupblKM/nV8+NA9Ffvr+w==",
+ "version": "16.11.6",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz",
+ "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==",
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.32.0.tgz",
- "integrity": "sha512-+OWTuWRSbWI1KDK8iEyG/6uK2rTm3kpS38wuVifGUTDB6kjEuNrzBI1MUtxnkneuWG/23QehABe2zHHrj+4yuA==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz",
+ "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==",
"dev": true,
"dependencies": {
- "@typescript-eslint/experimental-utils": "4.32.0",
- "@typescript-eslint/scope-manager": "4.32.0",
+ "@typescript-eslint/experimental-utils": "4.33.0",
+ "@typescript-eslint/scope-manager": "4.33.0",
"debug": "^4.3.1",
"functional-red-black-tree": "^1.0.1",
"ignore": "^5.1.8",
@@ -334,15 +334,15 @@
}
},
"node_modules/@typescript-eslint/experimental-utils": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.32.0.tgz",
- "integrity": "sha512-WLoXcc+cQufxRYjTWr4kFt0DyEv6hDgSaFqYhIzQZ05cF+kXfqXdUh+//kgquPJVUBbL3oQGKQxwPbLxHRqm6A==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz",
+ "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==",
"dev": true,
"dependencies": {
"@types/json-schema": "^7.0.7",
- "@typescript-eslint/scope-manager": "4.32.0",
- "@typescript-eslint/types": "4.32.0",
- "@typescript-eslint/typescript-estree": "4.32.0",
+ "@typescript-eslint/scope-manager": "4.33.0",
+ "@typescript-eslint/types": "4.33.0",
+ "@typescript-eslint/typescript-estree": "4.33.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
},
@@ -357,15 +357,33 @@
"eslint": "*"
}
},
- "node_modules/@typescript-eslint/parser": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.32.0.tgz",
- "integrity": "sha512-lhtYqQ2iEPV5JqV7K+uOVlPePjClj4dOw7K4/Z1F2yvjIUvyr13yJnDzkK6uon4BjHYuHy3EG0c2Z9jEhFk56w==",
+ "node_modules/@typescript-eslint/experimental-utils/node_modules/eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
"dev": true,
"dependencies": {
- "@typescript-eslint/scope-manager": "4.32.0",
- "@typescript-eslint/types": "4.32.0",
- "@typescript-eslint/typescript-estree": "4.32.0",
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "engines": {
+ "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=5"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz",
+ "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "4.33.0",
+ "@typescript-eslint/types": "4.33.0",
+ "@typescript-eslint/typescript-estree": "4.33.0",
"debug": "^4.3.1"
},
"engines": {
@@ -385,13 +403,13 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.32.0.tgz",
- "integrity": "sha512-DK+fMSHdM216C0OM/KR1lHXjP1CNtVIhJ54kQxfOE6x8UGFAjha8cXgDMBEIYS2XCYjjCtvTkjQYwL3uvGOo0w==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz",
+ "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "4.32.0",
- "@typescript-eslint/visitor-keys": "4.32.0"
+ "@typescript-eslint/types": "4.33.0",
+ "@typescript-eslint/visitor-keys": "4.33.0"
},
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
@@ -402,9 +420,9 @@
}
},
"node_modules/@typescript-eslint/types": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.32.0.tgz",
- "integrity": "sha512-LE7Z7BAv0E2UvqzogssGf1x7GPpUalgG07nGCBYb1oK4mFsOiFC/VrSMKbZQzFJdN2JL5XYmsx7C7FX9p9ns0w==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz",
+ "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==",
"dev": true,
"engines": {
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
@@ -415,13 +433,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.32.0.tgz",
- "integrity": "sha512-tRYCgJ3g1UjMw1cGG8Yn1KzOzNlQ6u1h9AmEtPhb5V5a1TmiHWcRyF/Ic+91M4f43QeChyYlVTcf3DvDTZR9vw==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz",
+ "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "4.32.0",
- "@typescript-eslint/visitor-keys": "4.32.0",
+ "@typescript-eslint/types": "4.33.0",
+ "@typescript-eslint/visitor-keys": "4.33.0",
"debug": "^4.3.1",
"globby": "^11.0.3",
"is-glob": "^4.0.1",
@@ -442,12 +460,12 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.32.0.tgz",
- "integrity": "sha512-e7NE0qz8W+atzv3Cy9qaQ7BTLwWsm084Z0c4nIO2l3Bp6u9WIgdqCgyPyV5oSPDMIW3b20H59OOCmVk3jw3Ptw==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz",
+ "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==",
"dev": true,
"dependencies": {
- "@typescript-eslint/types": "4.32.0",
+ "@typescript-eslint/types": "4.33.0",
"eslint-visitor-keys": "^2.0.0"
},
"engines": {
@@ -1013,33 +1031,6 @@
}
},
"node_modules/eslint-utils": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
- "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
- "dev": true,
- "dependencies": {
- "eslint-visitor-keys": "^2.0.0"
- },
- "engines": {
- "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/mysticatea"
- },
- "peerDependencies": {
- "eslint": ">=5"
- }
- },
- "node_modules/eslint-visitor-keys": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
- "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
- "dev": true,
- "engines": {
- "node": ">=10"
- }
- },
- "node_modules/eslint/node_modules/eslint-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
"integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
@@ -1054,7 +1045,7 @@
"url": "https://github.com/sponsors/mysticatea"
}
},
- "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+ "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
@@ -1063,6 +1054,15 @@
"node": ">=4"
}
},
+ "node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/eslint/node_modules/ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@@ -1121,9 +1121,9 @@
}
},
"node_modules/esquery/node_modules/estraverse": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
- "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"engines": {
"node": ">=4.0"
@@ -1142,9 +1142,9 @@
}
},
"node_modules/esrecurse/node_modules/estraverse": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
- "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"engines": {
"node": ">=4.0"
@@ -1362,9 +1362,9 @@
}
},
"node_modules/globals": {
- "version": "13.11.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz",
- "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==",
+ "version": "13.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz",
+ "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==",
"dev": true,
"dependencies": {
"type-fest": "^0.20.2"
@@ -1424,9 +1424,9 @@
}
},
"node_modules/husky": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.2.tgz",
- "integrity": "sha512-8yKEWNX4z2YsofXAMT7KvA1g8p+GxtB1ffV8XtpAEGuXNAbCV5wdNKH+qTpw8SM9fh4aMPDR+yQuKfgnreyZlg==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz",
+ "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==",
"dev": true,
"bin": {
"husky": "lib/bin.js"
@@ -1439,9 +1439,9 @@
}
},
"node_modules/ignore": {
- "version": "5.1.8",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
- "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz",
+ "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==",
"dev": true,
"engines": {
"node": ">= 4"
@@ -1519,9 +1519,9 @@
}
},
"node_modules/is-glob": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.2.tgz",
- "integrity": "sha512-ZZTOjRcDjuAAAv2cTBQP/lL59ZTArx77+7UzHdWW/XB1mrfp7DEaVpKmZ0XIzx+M7AxfhKcqV+nMetUQmFifwg==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
@@ -1625,12 +1625,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/lodash.clonedeep": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
- "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
- "dev": true
- },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -1712,9 +1706,9 @@
}
},
"node_modules/mocha": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.2.tgz",
- "integrity": "sha512-ta3LtJ+63RIBP03VBjMGtSqbe6cWXRejF9SyM9Zyli1CKZJZ+vfCTj3oW24V7wAphMJdpOFLoMI3hjJ1LWbs0w==",
+ "version": "9.1.3",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz",
+ "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==",
"dev": true,
"dependencies": {
"@ungap/promise-all-settled": "1.1.2",
@@ -2278,17 +2272,16 @@
}
},
"node_modules/table": {
- "version": "6.7.1",
- "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz",
- "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==",
+ "version": "6.7.3",
+ "resolved": "https://registry.npmjs.org/table/-/table-6.7.3.tgz",
+ "integrity": "sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw==",
"dev": true,
"dependencies": {
"ajv": "^8.0.1",
- "lodash.clonedeep": "^4.5.0",
"lodash.truncate": "^4.4.2",
"slice-ansi": "^4.0.0",
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0"
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=10.0.0"
@@ -2335,12 +2328,12 @@
}
},
"node_modules/ts-node": {
- "version": "10.2.1",
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz",
- "integrity": "sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==",
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz",
+ "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==",
"dev": true,
"dependencies": {
- "@cspotcode/source-map-support": "0.6.1",
+ "@cspotcode/source-map-support": "0.7.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
@@ -2360,9 +2353,6 @@
"ts-node-transpile-only": "dist/bin-transpile.js",
"ts-script": "dist/bin-script-deprecated.js"
},
- "engines": {
- "node": ">=12.0.0"
- },
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
@@ -2462,9 +2452,9 @@
}
},
"node_modules/typescript": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
- "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
+ "version": "4.4.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
+ "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -2654,12 +2644,12 @@
"dev": true
},
"@babel/highlight": {
- "version": "7.14.5",
- "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.5.tgz",
- "integrity": "sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg==",
+ "version": "7.16.0",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
+ "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
"dev": true,
"requires": {
- "@babel/helper-validator-identifier": "^7.14.5",
+ "@babel/helper-validator-identifier": "^7.15.7",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
},
@@ -2729,9 +2719,9 @@
"dev": true
},
"@cspotcode/source-map-support": {
- "version": "0.6.1",
- "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.6.1.tgz",
- "integrity": "sha512-DX3Z+T5dt1ockmPdobJS/FAsQPW4V4SrWEhD2iYQT2Cb2tQsiMnYxrcUH9By/Z3B+v0S5LMBkQtV/XOBbpLEOg==",
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
+ "integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
"dev": true,
"requires": {
"@cspotcode/source-map-consumer": "0.8.0"
@@ -2774,9 +2764,9 @@
}
},
"@humanwhocodes/object-schema": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.0.tgz",
- "integrity": "sha512-wdppn25U8z/2yiaT6YGquE6X8sSv7hNMWSXYSSU1jGv/yd6XqjXgTDJ8KP4NgjTXfJ3GbRjeeb8RTV7a/VpM+w==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==",
"dev": true
},
"@nodelib/fs.scandir": {
@@ -2848,19 +2838,19 @@
"dev": true
},
"@types/node": {
- "version": "16.10.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-16.10.1.tgz",
- "integrity": "sha512-4/Z9DMPKFexZj/Gn3LylFgamNKHm4K3QDi0gz9B26Uk0c8izYf97B5fxfpspMNkWlFupblKM/nV8+NA9Ffvr+w==",
+ "version": "16.11.6",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.6.tgz",
+ "integrity": "sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==",
"dev": true
},
"@typescript-eslint/eslint-plugin": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.32.0.tgz",
- "integrity": "sha512-+OWTuWRSbWI1KDK8iEyG/6uK2rTm3kpS38wuVifGUTDB6kjEuNrzBI1MUtxnkneuWG/23QehABe2zHHrj+4yuA==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz",
+ "integrity": "sha512-aINiAxGVdOl1eJyVjaWn/YcVAq4Gi/Yo35qHGCnqbWVz61g39D0h23veY/MA0rFFGfxK7TySg2uwDeNv+JgVpg==",
"dev": true,
"requires": {
- "@typescript-eslint/experimental-utils": "4.32.0",
- "@typescript-eslint/scope-manager": "4.32.0",
+ "@typescript-eslint/experimental-utils": "4.33.0",
+ "@typescript-eslint/scope-manager": "4.33.0",
"debug": "^4.3.1",
"functional-red-black-tree": "^1.0.1",
"ignore": "^5.1.8",
@@ -2870,55 +2860,66 @@
}
},
"@typescript-eslint/experimental-utils": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.32.0.tgz",
- "integrity": "sha512-WLoXcc+cQufxRYjTWr4kFt0DyEv6hDgSaFqYhIzQZ05cF+kXfqXdUh+//kgquPJVUBbL3oQGKQxwPbLxHRqm6A==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.33.0.tgz",
+ "integrity": "sha512-zeQjOoES5JFjTnAhI5QY7ZviczMzDptls15GFsI6jyUOq0kOf9+WonkhtlIhh0RgHRnqj5gdNxW5j1EvAyYg6Q==",
"dev": true,
"requires": {
"@types/json-schema": "^7.0.7",
- "@typescript-eslint/scope-manager": "4.32.0",
- "@typescript-eslint/types": "4.32.0",
- "@typescript-eslint/typescript-estree": "4.32.0",
+ "@typescript-eslint/scope-manager": "4.33.0",
+ "@typescript-eslint/types": "4.33.0",
+ "@typescript-eslint/typescript-estree": "4.33.0",
"eslint-scope": "^5.1.1",
"eslint-utils": "^3.0.0"
+ },
+ "dependencies": {
+ "eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dev": true,
+ "requires": {
+ "eslint-visitor-keys": "^2.0.0"
+ }
+ }
}
},
"@typescript-eslint/parser": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.32.0.tgz",
- "integrity": "sha512-lhtYqQ2iEPV5JqV7K+uOVlPePjClj4dOw7K4/Z1F2yvjIUvyr13yJnDzkK6uon4BjHYuHy3EG0c2Z9jEhFk56w==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.33.0.tgz",
+ "integrity": "sha512-ZohdsbXadjGBSK0/r+d87X0SBmKzOq4/S5nzK6SBgJspFo9/CUDJ7hjayuze+JK7CZQLDMroqytp7pOcFKTxZA==",
"dev": true,
"requires": {
- "@typescript-eslint/scope-manager": "4.32.0",
- "@typescript-eslint/types": "4.32.0",
- "@typescript-eslint/typescript-estree": "4.32.0",
+ "@typescript-eslint/scope-manager": "4.33.0",
+ "@typescript-eslint/types": "4.33.0",
+ "@typescript-eslint/typescript-estree": "4.33.0",
"debug": "^4.3.1"
}
},
"@typescript-eslint/scope-manager": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.32.0.tgz",
- "integrity": "sha512-DK+fMSHdM216C0OM/KR1lHXjP1CNtVIhJ54kQxfOE6x8UGFAjha8cXgDMBEIYS2XCYjjCtvTkjQYwL3uvGOo0w==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.33.0.tgz",
+ "integrity": "sha512-5IfJHpgTsTZuONKbODctL4kKuQje/bzBRkwHE8UOZ4f89Zeddg+EGZs8PD8NcN4LdM3ygHWYB3ukPAYjvl/qbQ==",
"dev": true,
"requires": {
- "@typescript-eslint/types": "4.32.0",
- "@typescript-eslint/visitor-keys": "4.32.0"
+ "@typescript-eslint/types": "4.33.0",
+ "@typescript-eslint/visitor-keys": "4.33.0"
}
},
"@typescript-eslint/types": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.32.0.tgz",
- "integrity": "sha512-LE7Z7BAv0E2UvqzogssGf1x7GPpUalgG07nGCBYb1oK4mFsOiFC/VrSMKbZQzFJdN2JL5XYmsx7C7FX9p9ns0w==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.33.0.tgz",
+ "integrity": "sha512-zKp7CjQzLQImXEpLt2BUw1tvOMPfNoTAfb8l51evhYbOEEzdWyQNmHWWGPR6hwKJDAi+1VXSBmnhL9kyVTTOuQ==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.32.0.tgz",
- "integrity": "sha512-tRYCgJ3g1UjMw1cGG8Yn1KzOzNlQ6u1h9AmEtPhb5V5a1TmiHWcRyF/Ic+91M4f43QeChyYlVTcf3DvDTZR9vw==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.33.0.tgz",
+ "integrity": "sha512-rkWRY1MPFzjwnEVHsxGemDzqqddw2QbTJlICPD9p9I9LfsO8fdmfQPOX3uKfUaGRDFJbfrtm/sXhVXN4E+bzCA==",
"dev": true,
"requires": {
- "@typescript-eslint/types": "4.32.0",
- "@typescript-eslint/visitor-keys": "4.32.0",
+ "@typescript-eslint/types": "4.33.0",
+ "@typescript-eslint/visitor-keys": "4.33.0",
"debug": "^4.3.1",
"globby": "^11.0.3",
"is-glob": "^4.0.1",
@@ -2927,12 +2928,12 @@
}
},
"@typescript-eslint/visitor-keys": {
- "version": "4.32.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.32.0.tgz",
- "integrity": "sha512-e7NE0qz8W+atzv3Cy9qaQ7BTLwWsm084Z0c4nIO2l3Bp6u9WIgdqCgyPyV5oSPDMIW3b20H59OOCmVk3jw3Ptw==",
+ "version": "4.33.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.33.0.tgz",
+ "integrity": "sha512-uqi/2aSz9g2ftcHWf8uLPJA70rUv6yuMW5Bohw+bwcuzaxQIHaKFZCKGoGXIrc9vkTJ3+0txM73K0Hq3d5wgIg==",
"dev": true,
"requires": {
- "@typescript-eslint/types": "4.32.0",
+ "@typescript-eslint/types": "4.33.0",
"eslint-visitor-keys": "^2.0.0"
}
},
@@ -3316,23 +3317,6 @@
"v8-compile-cache": "^2.0.3"
},
"dependencies": {
- "eslint-utils": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
- "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
- "dev": true,
- "requires": {
- "eslint-visitor-keys": "^1.1.0"
- },
- "dependencies": {
- "eslint-visitor-keys": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
- "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
- "dev": true
- }
- }
- },
"ignore": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@@ -3368,12 +3352,20 @@
}
},
"eslint-utils": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
- "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
"dev": true,
"requires": {
- "eslint-visitor-keys": "^2.0.0"
+ "eslint-visitor-keys": "^1.1.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "dev": true
+ }
}
},
"eslint-visitor-keys": {
@@ -3417,9 +3409,9 @@
},
"dependencies": {
"estraverse": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
- "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true
}
}
@@ -3434,9 +3426,9 @@
},
"dependencies": {
"estraverse": {
- "version": "5.2.0",
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz",
- "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==",
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true
}
}
@@ -3604,9 +3596,9 @@
}
},
"globals": {
- "version": "13.11.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-13.11.0.tgz",
- "integrity": "sha512-08/xrJ7wQjK9kkkRoI3OFUBbLx4f+6x3SGwcPvQ0QH6goFDrOU2oyAWrmh3dJezu65buo+HBMzAMQy6rovVC3g==",
+ "version": "13.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.0.tgz",
+ "integrity": "sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg==",
"dev": true,
"requires": {
"type-fest": "^0.20.2"
@@ -3645,15 +3637,15 @@
"dev": true
},
"husky": {
- "version": "7.0.2",
- "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.2.tgz",
- "integrity": "sha512-8yKEWNX4z2YsofXAMT7KvA1g8p+GxtB1ffV8XtpAEGuXNAbCV5wdNKH+qTpw8SM9fh4aMPDR+yQuKfgnreyZlg==",
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz",
+ "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==",
"dev": true
},
"ignore": {
- "version": "5.1.8",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz",
- "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==",
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.9.tgz",
+ "integrity": "sha512-2zeMQpbKz5dhZ9IwL0gbxSW5w0NK/MSAMtNuhgIHEPmaU3vPdKPL0UdvUCXs5SS4JAwsBxysK5sFMW8ocFiVjQ==",
"dev": true
},
"import-fresh": {
@@ -3710,9 +3702,9 @@
"dev": true
},
"is-glob": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.2.tgz",
- "integrity": "sha512-ZZTOjRcDjuAAAv2cTBQP/lL59ZTArx77+7UzHdWW/XB1mrfp7DEaVpKmZ0XIzx+M7AxfhKcqV+nMetUQmFifwg==",
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"requires": {
"is-extglob": "^2.1.1"
@@ -3789,12 +3781,6 @@
"p-locate": "^5.0.0"
}
},
- "lodash.clonedeep": {
- "version": "4.5.0",
- "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
- "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=",
- "dev": true
- },
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3858,9 +3844,9 @@
}
},
"mocha": {
- "version": "9.1.2",
- "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.2.tgz",
- "integrity": "sha512-ta3LtJ+63RIBP03VBjMGtSqbe6cWXRejF9SyM9Zyli1CKZJZ+vfCTj3oW24V7wAphMJdpOFLoMI3hjJ1LWbs0w==",
+ "version": "9.1.3",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-9.1.3.tgz",
+ "integrity": "sha512-Xcpl9FqXOAYqI3j79pEtHBBnQgVXIhpULjGQa7DVb0Po+VzmSIK9kanAiWLHoRR/dbZ2qpdPshuXr8l1VaHCzw==",
"dev": true,
"requires": {
"@ungap/promise-all-settled": "1.1.2",
@@ -4234,17 +4220,16 @@
}
},
"table": {
- "version": "6.7.1",
- "resolved": "https://registry.npmjs.org/table/-/table-6.7.1.tgz",
- "integrity": "sha512-ZGum47Yi6KOOFDE8m223td53ath2enHcYLgOCjGr5ngu8bdIARQk6mN/wRMv4yMRcHnCSnHbCEha4sobQx5yWg==",
+ "version": "6.7.3",
+ "resolved": "https://registry.npmjs.org/table/-/table-6.7.3.tgz",
+ "integrity": "sha512-5DkIxeA7XERBqMwJq0aHZOdMadBx4e6eDoFRuyT5VR82J0Ycg2DwM6GfA/EQAhJ+toRTaS1lIdSQCqgrmhPnlw==",
"dev": true,
"requires": {
"ajv": "^8.0.1",
- "lodash.clonedeep": "^4.5.0",
"lodash.truncate": "^4.4.2",
"slice-ansi": "^4.0.0",
- "string-width": "^4.2.0",
- "strip-ansi": "^6.0.0"
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
},
"dependencies": {
"ajv": {
@@ -4283,12 +4268,12 @@
}
},
"ts-node": {
- "version": "10.2.1",
- "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.2.1.tgz",
- "integrity": "sha512-hCnyOyuGmD5wHleOQX6NIjJtYVIO8bPP8F2acWkB4W06wdlkgyvJtubO/I9NkI88hCFECbsEgoLc0VNkYmcSfw==",
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.4.0.tgz",
+ "integrity": "sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A==",
"dev": true,
"requires": {
- "@cspotcode/source-map-support": "0.6.1",
+ "@cspotcode/source-map-support": "0.7.0",
"@tsconfig/node10": "^1.0.7",
"@tsconfig/node12": "^1.0.7",
"@tsconfig/node14": "^1.0.0",
@@ -4358,9 +4343,9 @@
"dev": true
},
"typescript": {
- "version": "4.4.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
- "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
+ "version": "4.4.4",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
+ "integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
"dev": true
},
"uri-js": {
diff --git a/package.json b/package.json
index 65fe6e0..99d53aa 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"build": "ncc build src/main.ts",
"lint": "eslint . --ext .ts,.tsx",
"format": "prettier --write **/*.ts",
- "test": "mocha -r ts-node/register -t 120s 'tests/*.test.ts'"
+ "test": "mocha -r ts-node/register -t 120s 'tests/**/*.test.ts'"
},
"repository": {
"type": "git",
diff --git a/src/base.ts b/src/base.ts
index 1ed0cc2..f51553d 100644
--- a/src/base.ts
+++ b/src/base.ts
@@ -5,7 +5,7 @@ import {
GoogleAccessTokenResponse,
GoogleIDTokenParameters,
GoogleIDTokenResponse,
-} from './actionauth';
+} from './client/auth_client';
export class BaseClient {
/**
@@ -132,8 +132,8 @@ export class BaseClient {
accessToken: parsed['accessToken'],
expiration: parsed['expireTime'],
};
- } catch (err) {
- throw new Error(`failed to generate Google Cloud access token for ${serviceAccount}: ${err}`);
+ } catch (e) {
+ throw new Error(`Failed to generate Google Cloud access token for ${serviceAccount}: ${e}`);
}
}
}
diff --git a/src/actionauth.ts b/src/client/auth_client.ts
similarity index 74%
rename from src/actionauth.ts
rename to src/client/auth_client.ts
index 128a467..d61095a 100644
--- a/src/actionauth.ts
+++ b/src/client/auth_client.ts
@@ -1,10 +1,11 @@
/**
* Defines the main interface for all clients that generate credentials.
*/
-export interface ActionAuth {
- getAccessToken(opts: GoogleAccessTokenParameters): Promise;
- getIDToken(opts: GoogleIDTokenParameters): Promise;
- createCredentialsFile(outputDir: string): Promise;
+export interface AuthClient {
+ getAuthToken(): Promise;
+ getProjectID(): Promise;
+ getServiceAccount(): Promise;
+ createCredentialsFile(outputDir: string): Promise;
}
/**
@@ -66,14 +67,3 @@ export interface GoogleIDTokenParameters {
export interface GoogleIDTokenResponse {
token: string;
}
-
-/**
- * CreateCredentialsFileResponse is the response from creating a credential file.
- *
- * @param credentialsPath Path to the created credentials file.
- * @param envVars Optional key value pairs that can be exported as env variables.
- */
-export interface CreateCredentialsFileResponse {
- credentialsPath: string;
- envVars?: Map;
-}
diff --git a/src/client/credentials_json_client.ts b/src/client/credentials_json_client.ts
new file mode 100644
index 0000000..18ee2f9
--- /dev/null
+++ b/src/client/credentials_json_client.ts
@@ -0,0 +1,128 @@
+'use strict';
+
+import { createSign } from 'crypto';
+import { AuthClient } from './auth_client';
+import { toBase64, fromBase64, trimmedString, writeSecureFile } from '../utils';
+
+/**
+ * Available options to create the CredentialsJSONClient.
+ *
+ * @param projectID User-supplied value for project ID. If not provided, the
+ * project ID is extracted from the credentials JSON.
+ * @param credentialsJSON Raw JSON credentials blob.
+ */
+interface CredentialsJSONClientOptions {
+ projectID?: string;
+ credentialsJSON: string;
+}
+
+/**
+ * CredentialsJSONClient is a client that accepts a service account key JSON
+ * credential.
+ */
+export class CredentialsJSONClient implements AuthClient {
+ readonly #projectID: string;
+ readonly #credentials: Record;
+
+ constructor(opts: CredentialsJSONClientOptions) {
+ this.#credentials = this.parseServiceAccountKeyJSON(opts.credentialsJSON);
+ this.#projectID = opts.projectID || this.#credentials['project_id'];
+ }
+
+ /**
+ * parseServiceAccountKeyJSON attempts to parse the given string as a service
+ * account key JSON. It handles if the string is base64-encoded.
+ */
+ parseServiceAccountKeyJSON(str: string): Record {
+ str = trimmedString(str);
+ if (!str) {
+ throw new Error(`Missing service account key JSON (got empty value)`);
+ }
+
+ // If the string doesn't start with a JSON object character, it is probably
+ // base64-encoded.
+ if (!str.startsWith('{')) {
+ str = fromBase64(str);
+ }
+
+ let creds: Record;
+ try {
+ creds = JSON.parse(str);
+ } catch (e) {
+ throw new SyntaxError(`Failed to parse credentials as JSON: ${e}`);
+ }
+
+ const requireValue = (key: string) => {
+ const val = trimmedString(creds[key]);
+ if (!val) {
+ throw new Error(`Service account key JSON is missing required field "${key}"`);
+ }
+ };
+
+ requireValue('project_id');
+ requireValue('private_key_id');
+ requireValue('private_key');
+ requireValue('client_email');
+
+ return creds;
+ }
+
+ /**
+ * getAuthToken generates a token capable of calling the iamcredentials API.
+ */
+ async getAuthToken(): Promise {
+ const header = {
+ alg: 'RS256',
+ typ: 'JWT',
+ kid: this.#credentials['private_key_id'],
+ };
+
+ const now = Math.floor(new Date().getTime() / 1000);
+
+ const body = {
+ iss: this.#credentials['client_email'],
+ sub: this.#credentials['client_email'],
+ aud: 'https://iamcredentials.googleapis.com/',
+ iat: now,
+ exp: now + 3599,
+ };
+
+ const message = toBase64(JSON.stringify(header)) + '.' + toBase64(JSON.stringify(body));
+
+ try {
+ const signer = createSign('RSA-SHA256');
+ signer.write(message);
+ signer.end();
+
+ const signature = signer.sign(this.#credentials['private_key']);
+ return message + '.' + toBase64(signature);
+ } catch (e) {
+ throw new Error(`Failed to sign auth token: ${e}`);
+ }
+ }
+
+ /**
+ * getProjectID returns the project ID. If an override was given, the override
+ * is returned. Otherwise, this will be the project ID that was extracted from
+ * the service account key JSON.
+ */
+ async getProjectID(): Promise {
+ return this.#projectID;
+ }
+
+ /**
+ * getServiceAccount returns the service account email for the authentication,
+ * extracted from the Service Account Key JSON.
+ */
+ async getServiceAccount(): Promise {
+ return this.#credentials['client_email'];
+ }
+
+ /**
+ * createCredentialsFile creates a Google Cloud credentials file that can be
+ * set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries.
+ */
+ async createCredentialsFile(outputDir: string): Promise {
+ return await writeSecureFile(outputDir, JSON.stringify(this.#credentials));
+ }
+}
diff --git a/src/client/workload_identity_client.ts b/src/client/workload_identity_client.ts
new file mode 100644
index 0000000..0135055
--- /dev/null
+++ b/src/client/workload_identity_client.ts
@@ -0,0 +1,175 @@
+'use strict';
+
+import { URL } from 'url';
+import { AuthClient } from './auth_client';
+import { writeSecureFile } from '../utils';
+import { BaseClient } from '../base';
+
+/**
+ * Available options to create the WorkloadIdentityClient.
+ *
+ * @param projectID User-supplied value for project ID. If not provided, the
+ * project ID is extracted from the service account email.
+ * @param providerID Full path (including project, location, etc) to the Google
+ * Cloud Workload Identity Provider.
+ * @param serviceAccount Email address or unique identifier of the service
+ * account to impersonate
+ * @param token GitHub OIDC token to use for exchanging with Workload Identity
+ * Federation.
+ * @param audience The value for the audience parameter in the generated GitHub
+ * Actions OIDC token, defaults to the value of workload_identity_provider
+ */
+interface WorkloadIdentityClientOptions {
+ projectID?: string;
+ providerID: string;
+ serviceAccount: string;
+ token: string;
+ audience: string;
+}
+
+/**
+ * WorkloadIdentityClient is a client that uses the GitHub Actions runtime to
+ * authentication via Workload Identity.
+ */
+export class WorkloadIdentityClient implements AuthClient {
+ readonly #projectID: string;
+ readonly #providerID: string;
+ readonly #serviceAccount: string;
+ readonly #token: string;
+ readonly #audience: string;
+
+ constructor(opts: WorkloadIdentityClientOptions) {
+ this.#providerID = opts.providerID;
+ this.#serviceAccount = opts.serviceAccount;
+ this.#token = opts.token;
+ this.#audience = opts.audience;
+
+ this.#projectID =
+ opts.projectID || this.extractProjectIDFromServiceAccountEmail(this.#serviceAccount);
+ }
+
+ /**
+ * extractProjectIDFromServiceAccountEmail extracts the project ID from the
+ * service account email address.
+ */
+ extractProjectIDFromServiceAccountEmail(str: string): string {
+ if (!str) {
+ return '';
+ }
+
+ const [, dn] = str.split('@', 2);
+ if (!str.endsWith('.iam.gserviceaccount.com')) {
+ throw new Error(
+ `Service account email ${str} is not of the form ` +
+ `"[name]@[project].iam.gserviceaccount.com. You must manually ` +
+ `specify the "project_id" parameter in your GitHub Actions workflow.`,
+ );
+ }
+
+ const [project] = dn.split('.', 2);
+ return project;
+ }
+
+ /**
+ * getAuthToken generates a Google Cloud federated token using the provided OIDC
+ * token and Workload Identity Provider.
+ */
+ async getAuthToken(): Promise {
+ const stsURL = new URL('https://sts.googleapis.com/v1/token');
+
+ const data = {
+ audience: '//iam.googleapis.com/' + this.#providerID,
+ grantType: 'urn:ietf:params:oauth:grant-type:token-exchange',
+ requestedTokenType: 'urn:ietf:params:oauth:token-type:access_token',
+ scope: 'https://www.googleapis.com/auth/cloud-platform',
+ subjectTokenType: 'urn:ietf:params:oauth:token-type:jwt',
+ subjectToken: this.#token,
+ };
+
+ const opts = {
+ hostname: stsURL.hostname,
+ port: stsURL.port,
+ path: stsURL.pathname + stsURL.search,
+ method: 'POST',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ },
+ };
+
+ try {
+ const resp = await BaseClient.request(opts, JSON.stringify(data));
+ const parsed = JSON.parse(resp);
+ return parsed['access_token'];
+ } catch (err) {
+ throw new Error(
+ `Failed to generate Google Cloud federated token for ${this.#providerID}: ${err}`,
+ );
+ }
+ }
+
+ /**
+ * getProjectID returns the project ID. If an override was given, the override
+ * is returned. Otherwise, this will be the project ID that was extracted from
+ * the service account key JSON.
+ */
+ async getProjectID(): Promise {
+ return this.#projectID;
+ }
+
+ /**
+ * getServiceAccount returns the service account email for the authentication,
+ * extracted from the input parameter.
+ */
+ async getServiceAccount(): Promise {
+ return this.#serviceAccount;
+ }
+
+ /**
+ * createCredentialsFile creates a Google Cloud credentials file that can be
+ * set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries.
+ */
+ async createCredentialsFile(outputDir: string): Promise {
+ // Extract the request token and request URL from the environment. These
+ // are only set when an id-token is requested and the submitter has
+ // collaborator permissions.
+ const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
+ const requestURLRaw = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
+ if (!requestToken || !requestURLRaw) {
+ throw new Error(
+ 'GitHub Actions did not inject $ACTIONS_ID_TOKEN_REQUEST_TOKEN or ' +
+ '$ACTIONS_ID_TOKEN_REQUEST_URL into this job. This most likely ' +
+ 'means the GitHub Actions workflow permissions are incorrect, or ' +
+ 'this job is being run from a fork. For more information, please ' +
+ 'see the GitHub documentation at https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token',
+ );
+ }
+ const requestURL = new URL(requestURLRaw);
+
+ // Append the audience value to the request.
+ const params = requestURL.searchParams;
+ params.set('audience', this.#audience);
+ requestURL.search = params.toString();
+ const data = {
+ type: 'external_account',
+ audience: `//iam.googleapis.com/${this.#providerID}`,
+ 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/${
+ this.#serviceAccount
+ }:generateAccessToken`,
+ credential_source: {
+ url: requestURL,
+ headers: {
+ Authorization: `Bearer ${requestToken}`,
+ },
+ format: {
+ type: 'json',
+ subject_token_field_name: 'value',
+ },
+ },
+ };
+
+ return await writeSecureFile(outputDir, JSON.stringify(data));
+ }
+}
diff --git a/src/main.ts b/src/main.ts
index 6d99219..2cd58a4 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,8 +1,10 @@
'use strict';
import * as core from '@actions/core';
-import { WIFClient } from './workload_identity';
-import { ActionAuth } from './actionauth';
+import { WorkloadIdentityClient } from './client/workload_identity_client';
+import { CredentialsJSONClient } from './client/credentials_json_client';
+import { AuthClient } from './client/auth_client';
+import { BaseClient } from './base';
import { explodeStrings } from './utils';
/**
@@ -11,22 +13,53 @@ import { explodeStrings } from './utils';
async function run(): Promise {
try {
// Load configuration.
- const workloadIdentityProvider = core.getInput('workload_identity_provider', {
- required: true,
- });
- const serviceAccount = core.getInput('service_account', { required: true });
- // audience will default to the WIF provider ID when used with WIF
- const audience = core.getInput('audience');
+ const projectID = core.getInput('project_id');
+ const workloadIdentityProvider = core.getInput('workload_identity_provider');
+ const serviceAccount = core.getInput('service_account');
+ const audience =
+ core.getInput('audience') || `https://iam.googleapis.com/${workloadIdentityProvider}`;
+ const credentialsJSON = core.getInput('credentials_json');
const createCredentialsFile = core.getBooleanInput('create_credentials_file');
- const activateCredentialsFile = core.getBooleanInput('activate_credentials_file');
const tokenFormat = core.getInput('token_format');
const delegates = explodeStrings(core.getInput('delegates'));
- const client: ActionAuth = new WIFClient({
- providerID: workloadIdentityProvider,
- serviceAccount: serviceAccount,
- audience: audience,
- });
+ // Ensure exactly one of workload_identity_provider and credentials_json was
+ // provided.
+ if (
+ (!workloadIdentityProvider && !credentialsJSON) ||
+ (workloadIdentityProvider && credentialsJSON)
+ ) {
+ throw new Error(
+ 'The GitHub Action workflow must specify exactly one of ' +
+ '"workload_identity_provider" or "credentials_json"!',
+ );
+ }
+
+ // Ensure a service_account was provided if using WIF.
+ if (workloadIdentityProvider && !serviceAccount) {
+ throw new Error(
+ 'The GitHub Action workflow must specify a "service_account" to ' +
+ 'impersonate when using "workload_identity_provider"!',
+ );
+ }
+
+ // Instantiate the correct client based on the provided input parameters.
+ let client: AuthClient;
+ if (workloadIdentityProvider) {
+ const token = await core.getIDToken(audience);
+ client = new WorkloadIdentityClient({
+ projectID: projectID,
+ providerID: workloadIdentityProvider,
+ serviceAccount: serviceAccount,
+ token: token,
+ audience: audience,
+ });
+ } else {
+ client = new CredentialsJSONClient({
+ projectID: projectID,
+ credentialsJSON: credentialsJSON,
+ });
+ }
// Always write the credentials file first, before trying to generate
// tokens. This will ensure the file is written even if token generation
@@ -38,18 +71,21 @@ async function run(): Promise {
throw new Error('$RUNNER_TEMP is not set');
}
- const { credentialsPath, envVars } = await client.createCredentialsFile(runnerTempDir);
+ const credentialsPath = await client.createCredentialsFile(runnerTempDir);
core.setOutput('credentials_file_path', credentialsPath);
-
- // Also set the magic environment variable for gcloud and SDKs if
- // requested.
- if (activateCredentialsFile && envVars) {
- for (const [k, v] of envVars) {
- core.exportVariable(k, v);
- }
- }
+ core.exportVariable('CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE', credentialsPath);
+ core.exportVariable('GOOGLE_APPLICATION_CREDENTIALS', credentialsPath);
}
+ // Set the project ID environment variables to the computed values.
+ const computedProjectID = await client.getProjectID();
+ core.setOutput('project_id', computedProjectID);
+ core.exportVariable('CLOUDSDK_PROJECT', computedProjectID);
+ core.exportVariable('CLOUDSDK_CORE_PROJECT', computedProjectID);
+ core.exportVariable('GCP_PROJECT', computedProjectID);
+ core.exportVariable('GCLOUD_PROJECT', computedProjectID);
+ core.exportVariable('GOOGLE_CLOUD_PROJECT', computedProjectID);
+
switch (tokenFormat) {
case '': {
break;
@@ -60,8 +96,10 @@ async function run(): Promise {
case 'access_token': {
const accessTokenLifetime = core.getInput('access_token_lifetime');
const accessTokenScopes = explodeStrings(core.getInput('access_token_scopes'));
+ const serviceAccount = await client.getServiceAccount();
- const { accessToken, expiration } = await client.getAccessToken({
+ const authToken = await client.getAuthToken();
+ const { accessToken, expiration } = await BaseClient.googleAccessToken(authToken, {
serviceAccount,
delegates,
scopes: accessTokenScopes,
@@ -76,7 +114,10 @@ async function run(): Promise {
case 'id_token': {
const idTokenAudience = core.getInput('id_token_audience', { required: true });
const idTokenIncludeEmail = core.getBooleanInput('id_token_include_email');
- const { token } = await client.getIDToken({
+ const serviceAccount = await client.getServiceAccount();
+
+ const authToken = await client.getAuthToken();
+ const { token } = await BaseClient.googleIDToken(authToken, {
serviceAccount,
audience: idTokenAudience,
delegates,
@@ -87,7 +128,7 @@ async function run(): Promise {
break;
}
default: {
- throw new Error(`unknown token format "${tokenFormat}"`);
+ throw new Error(`Unknown token format "${tokenFormat}"`);
}
}
} catch (err) {
diff --git a/src/utils.ts b/src/utils.ts
index 9bf284c..94ded1c 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -3,14 +3,14 @@ import crypto from 'crypto';
import path from 'path';
/**
- * writeCredFile writes a file to disk in a given directory with a
+ * writeSecureFile writes a file to disk in a given directory with a
* random name.
*
* @param outputDir Directory to create random file in.
* @param data Data to write to file.
* @returns Path to written file.
*/
-export async function writeCredFile(outputDir: string, data: string): Promise {
+export async function writeSecureFile(outputDir: string, data: string): Promise {
// Generate a random filename to store the credential. 12 bytes is 24
// characters in hex. It's not the ideal entropy, but we have to be under
// the 255 character limit for Windows filenames (which includes their
@@ -45,3 +45,32 @@ export function explodeStrings(input: string): Array {
}
return list;
}
+
+/**
+ * toBase64 base64 URL encodes the result.
+ */
+export function toBase64(s: string | Buffer): string {
+ return Buffer.from(s)
+ .toString('base64')
+ .replace(/\+/g, '-')
+ .replace(/\//g, '_')
+ .replace(/=+$/, '');
+}
+
+/**
+ * fromBase64 base64 decodes the result, taking into account URL and standard
+ * encoding with and without padding.
+ */
+export function fromBase64(s: string): string {
+ const str = s.replace(/-/g, '+').replace(/_/g, '/');
+ while (str.length % 4) s += '=';
+ return Buffer.from(str, 'base64').toString('utf8');
+}
+
+/**
+ * trimmedString returns a string trimmed of whitespace. If the input string is
+ * null, then it returns the empty string.
+ */
+export function trimmedString(s: string): string {
+ return s ? s.trim() : '';
+}
diff --git a/src/workload_identity.ts b/src/workload_identity.ts
deleted file mode 100644
index 2a6e857..0000000
--- a/src/workload_identity.ts
+++ /dev/null
@@ -1,178 +0,0 @@
-'use strict';
-
-import { URL } from 'url';
-import * as core from '@actions/core';
-import {
- ActionAuth,
- CreateCredentialsFileResponse,
- GoogleAccessTokenParameters,
- GoogleAccessTokenResponse,
- GoogleIDTokenParameters,
- GoogleIDTokenResponse,
-} from './actionauth';
-import { writeCredFile } from './utils';
-import { BaseClient } from './base';
-
-/**
- * GoogleFederatedTokenParameters are the parameters to generate a Federated
- * Identity Token as described in:
- *
- * https://cloud.google.com/iam/docs/access-resources-oidc#exchange-token
- *
- * @param providerID Full path (including project, location, etc) to the Google
- * Cloud Workload Identity Provider.
- * @param token OIDC token to exchange for a Google Cloud federated token.
- */
-interface GoogleFederatedTokenParameters {
- providerID: string;
- token: string;
-}
-
-/**
- * Available options to create the WIF client.
- *
- * @param providerID Full path (including project, location, etc) to the Google
- * Cloud Workload Identity Provider.
- * @param serviceAccount Email address or unique identifier of the service
- * account to impersonate
- * @param audience The value for the audience parameter in the generated GitHub Actions OIDC token,
- * defaults to the value of workload_identity_provider
- */
-interface WIFClientOptions {
- providerID: string;
- serviceAccount: string;
- audience?: string;
-}
-
-export class WIFClient implements ActionAuth {
- readonly providerID: string;
- readonly serviceAccount: string;
- readonly audience: string;
-
- constructor(opts: WIFClientOptions) {
- this.providerID = opts.providerID;
- this.serviceAccount = opts.serviceAccount;
- this.audience = opts.audience ? opts.audience : `https://iam.googleapis.com/${this.providerID}`;
- }
-
- /**
- * googleFederatedToken generates a Google Cloud federated token using the
- * provided OIDC token and Workload Identity Provider.
- */
- static async googleFederatedToken({
- providerID,
- token,
- }: GoogleFederatedTokenParameters): Promise {
- const stsURL = new URL('https://sts.googleapis.com/v1/token');
-
- const data = {
- audience: '//iam.googleapis.com/' + providerID,
- grantType: 'urn:ietf:params:oauth:grant-type:token-exchange',
- requestedTokenType: 'urn:ietf:params:oauth:token-type:access_token',
- scope: 'https://www.googleapis.com/auth/cloud-platform',
- subjectTokenType: 'urn:ietf:params:oauth:token-type:jwt',
- subjectToken: token,
- };
-
- const opts = {
- hostname: stsURL.hostname,
- port: stsURL.port,
- path: stsURL.pathname + stsURL.search,
- method: 'POST',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- },
- };
-
- try {
- const resp = await BaseClient.request(opts, JSON.stringify(data));
- const parsed = JSON.parse(resp);
- return parsed['access_token'];
- } catch (err) {
- throw new Error(`failed to generate Google Cloud federated token for ${providerID}: ${err}`);
- }
- }
-
- /**
- * getFederatedToken generates a Google Cloud federated token using the
- * GitHub OIDC token.
- */
- private async getFederatedToken(): Promise {
- // Get the GitHub OIDC token.
- const githubOIDCToken = await core.getIDToken(this.audience);
- // Exchange the GitHub OIDC token for a Google Federated Token.
- const googleFederatedToken = await WIFClient.googleFederatedToken({
- providerID: this.providerID,
- token: githubOIDCToken,
- });
- core.setSecret(googleFederatedToken);
- return googleFederatedToken;
- }
-
- /**
- * getAccessToken generates a Google Cloud access token for the provided
- * service account email or unique id.
- */
- async getAccessToken(opts: GoogleAccessTokenParameters): Promise {
- const googleFederatedToken = await this.getFederatedToken();
- return await BaseClient.googleAccessToken(googleFederatedToken, opts);
- }
-
- /**
- * getIDToken generates a Google Cloud ID token for the provided
- * service account email or unique id.
- */
- async getIDToken(tokenParams: GoogleIDTokenParameters): Promise {
- const googleFederatedToken = await this.getFederatedToken();
- return await BaseClient.googleIDToken(googleFederatedToken, tokenParams);
- }
-
- /**
- * createCredentialsFile creates a Google Cloud credentials file that can be
- * set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries.
- */
- async createCredentialsFile(outputDir: string): Promise {
- // Extract the request token and request URL from the environment. These
- // are only set when an id-token is requested and the submitter has
- // collaborator permissions.
- const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
- const requestURLRaw = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
- if (!requestToken || !requestURLRaw) {
- throw new Error(
- 'GitHub Actions did not inject $ACTIONS_ID_TOKEN_REQUEST_TOKEN or ' +
- '$ACTIONS_ID_TOKEN_REQUEST_URL into this job. This most likely ' +
- 'means the GitHub Actions workflow permissions are incorrect, or ' +
- 'this job is being run from a fork. For more information, please ' +
- 'see the GitHub documentation at https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token',
- );
- }
- const requestURL = new URL(requestURLRaw);
-
- // Append the audience value to the request.
- const params = requestURL.searchParams;
- params.set('audience', this.audience);
- requestURL.search = params.toString();
- const data = {
- type: 'external_account',
- audience: `//iam.googleapis.com/${this.providerID}`,
- 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/${this.serviceAccount}:generateAccessToken`,
- credential_source: {
- url: requestURL,
- headers: {
- Authorization: `Bearer ${requestToken}`,
- },
- format: {
- type: 'json',
- subject_token_field_name: 'value',
- },
- },
- };
-
- const credentialsPath = await writeCredFile(outputDir, JSON.stringify(data));
- const envVars = new Map([['GOOGLE_APPLICATION_CREDENTIALS', credentialsPath]]);
- return { credentialsPath, envVars };
- }
-}
diff --git a/tests/client.test.ts b/tests/client.test.ts
deleted file mode 100644
index 5de6534..0000000
--- a/tests/client.test.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-import { expect } from 'chai';
-import 'mocha';
-
-describe('Client', () => {
- it('todo');
-});
diff --git a/tests/client/credentials_json_client.test.ts b/tests/client/credentials_json_client.test.ts
new file mode 100644
index 0000000..78f71f4
--- /dev/null
+++ b/tests/client/credentials_json_client.test.ts
@@ -0,0 +1,107 @@
+import 'mocha';
+import { expect } from 'chai';
+
+import { tmpdir } from 'os';
+import { readFileSync } from 'fs';
+import { CredentialsJSONClient } from '../../src/client/credentials_json_client';
+
+// Yes, this is a real private key. No, it's not valid for authenticating
+// Google Cloud.
+const credentialsJSON = `
+{
+ "type": "service_account",
+ "project_id": "my-project",
+ "private_key_id": "1234567890abcdefghijklmnopqrstuvwxyzaabb",
+ "private_key": "-----BEGIN PRIVATE KEY-----\\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCRVYIJRuxdujaX\\nUfyY9mXT1O0M3PwyT+FnPJVY+6Md7KMiPKpZRYt7okj51Ln1FLcb9mY17LzPEAxS\\nBPn1LWNpSJpmttI/D3U+bG/znf/E89ErVopYWpaynbYrb/Mu478IE9TgvnqJMlkj\\nlQbaxnZ7qhnbI5h6p/HINWfY7xBDGZM1sc2FK9KbNfEzLdW1YiK/lWAwtfM7rbiO\\nZj+LnWm2dgwZxu0h8m68qYYMywzLcV3NTe35qdAznasc1WQvJikY+N82Wu+HjsPa\\nH0fLE3gN5r+BzDYQxEQnWANgxlsHeN9mg5LAg5fyTBwTS7Ato/qQ07da0CSoS1M0\\nriYvuCzhAgMBAAECggEAAai+m9fG5B03kIMLpY5O7Rv9AM+ufb91hx6Nwkp7r4M5\\nt11vY7I96wuYJ92iBu8m4XR6fGw0Xz3gkcQ69ZCu5320hBdPrJsrqXwMhgxgoGcq\\nWuB8aJEWASi+T9hGENA++eDQFMupWV6HafzCdxd4NKAfmZ/xf1OFUu0TVpvxKlAD\\ne6Njz/5+QFdUcNioi7iGy1Qz7xdpClEWdVin8VWe3p6UsCLfHmQfPPuLXOvpBj6k\\niFu9dl93z+8vlDLoAyXSaDeYyRMBGVOBM36cICuVpxfV1s/corEZXhz3aI8mlYiQ\\n6YXTcEnllt+NTJDIL99CnYn+WBVzeIGXtr0EKAyM6QKBgQDCU6FDvU0P8qt45BDm\\nSP2V7uMoI32mjEA3plJzqqSZ9ritxFmylrOttOoTYH2FVjrKPZZsLihSjpmm+wEz\\nGfjd75eSJYAb/m7GNOqbJjqAJIbIMaHfVcH6ODT2b0Tc8v/CK0PZy/jzgt68TdtF\\no462tr8isj7yLpCGdoLq9iq4gwKBgQC/dWTGFnaI08v1uqx6derf+qikSsjlYh4L\\nDdTlI8/eaTR90PFPQ4a8LE8pmhMhkJNg87jAF5VF29sPmlpfKbOC87C2iI8uIHcn\\nu0sTdhn6SukyUSN/eeb1KSDJuxDvIgPRTZj6XMlUulADeLRnlAoWOe0tu/wqpse6\\nB0Qu2oAfywKBgQCMWukESyro1OZit585JQj7jQJG0HOFopETYK722g5vIdM7trDu\\nm4iFc0EJ48xlTOXDgv4tfp0jG9oA0BSKuzyT1+RK64j/LyMFR90XWGIyga9T0v1O\\nmNs1BfnC8JT1XRG7RZKJMZjLEQAdU8KHJt4CPDYLMmDifR1n8RsX59rtTwKBgQCS\\nnAmsKn1gb5cqt2Tmba+LDj3feSj3hjftTQ0u3kqKTNOWWM7AXLwrEl8YQ1TNChHh\\nVyCtcCGtmhrYiuETKDK/X259iHrj3paABUsLPw/Le1uxXTKqpiV2rKTf9XCVPd3g\\ng+RWK4E8cWNeFStIebNzq630rJP/8TDWQkQzALzGGwKBgQC5bnlmipIGhtX2pP92\\niBM8fJC7QXbyYyamriyFjC3o250hHy7mZZG7bd0bH3gw0NdC+OZIBNv7AoNhjsvP\\nuE0Qp/vQXpgHEeYFyfWn6PyHGzqKLFMZ/+iCTuy8Iebs1p5DZY8RMXpx4tv6NfRy\\nbxHUjlOgP7xmXM+OZpNymFlRkg==\\n-----END PRIVATE KEY-----\\n",
+ "client_email": "my-service-account@my-project.iam.gserviceaccount.com",
+ "client_id": "123456789098765432101",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/my-service-account%40my-project.iam.gserviceaccount.com"
+}
+`;
+
+describe('CredentialsJSONClient', () => {
+ describe('#parseServiceAccountKeyJSON', () => {
+ it('throws exception on invalid json', async () => {
+ const fn = (): CredentialsJSONClient => {
+ return new CredentialsJSONClient({
+ credentialsJSON: 'invalid json',
+ });
+ };
+
+ expect(fn).to.throw(SyntaxError);
+ });
+
+ it('handles base64', async () => {
+ const fn = (): CredentialsJSONClient => {
+ return new CredentialsJSONClient({
+ credentialsJSON: Buffer.from('{}').toString('base64'),
+ });
+ };
+
+ expect(fn).to.not.throw(SyntaxError);
+ });
+ });
+
+ describe('#getAuthToken', () => {
+ it('signs a jwt', async () => {
+ const client = new CredentialsJSONClient({
+ credentialsJSON: credentialsJSON,
+ });
+
+ const token = await client.getAuthToken();
+ expect(token).to.not.be.null;
+ });
+ });
+
+ describe('#getProjectID', () => {
+ it('extracts project ID from the json', async () => {
+ const client = new CredentialsJSONClient({
+ credentialsJSON: credentialsJSON,
+ });
+
+ const result = await client.getProjectID();
+ expect(result).to.eq('my-project');
+ });
+
+ it('prefers the override if given', async () => {
+ const client = new CredentialsJSONClient({
+ projectID: 'my-other-project',
+ credentialsJSON: credentialsJSON,
+ });
+
+ const result = await client.getProjectID();
+ expect(result).to.eq('my-other-project');
+ });
+ });
+
+ describe('#getServiceAccount', () => {
+ it('extracts service account from the json', async () => {
+ const client = new CredentialsJSONClient({
+ credentialsJSON: credentialsJSON,
+ });
+
+ const result = await client.getServiceAccount();
+ expect(result).to.eq('my-service-account@my-project.iam.gserviceaccount.com');
+ });
+ });
+
+ describe('#createCredentialsFile', () => {
+ it('writes the file', async () => {
+ const tmp = tmpdir();
+ const client = new CredentialsJSONClient({
+ credentialsJSON: credentialsJSON,
+ });
+
+ const exp = JSON.parse(credentialsJSON);
+
+ const pth = await client.createCredentialsFile(tmp);
+ const data = readFileSync(pth);
+ const got = JSON.parse(data.toString('utf8'));
+
+ expect(got).to.deep.equal(exp);
+ });
+ });
+});
diff --git a/tests/client/workload_identity_client.test.ts b/tests/client/workload_identity_client.test.ts
new file mode 100644
index 0000000..8c13910
--- /dev/null
+++ b/tests/client/workload_identity_client.test.ts
@@ -0,0 +1,102 @@
+import 'mocha';
+import { expect } from 'chai';
+
+import { tmpdir } from 'os';
+import { readFileSync } from 'fs';
+import { WorkloadIdentityClient } from '../../src/client/workload_identity_client';
+
+describe('WorkloadIdentityClient', () => {
+ describe('#getProjectID', () => {
+ it('extracts project ID from the service account email', async () => {
+ const client = new WorkloadIdentityClient({
+ providerID: 'my-provider',
+ token: 'my-token',
+ serviceAccount: 'my-service@my-project.iam.gserviceaccount.com',
+ audience: 'my-aud',
+ });
+
+ const result = await client.getProjectID();
+ expect(result).to.eq('my-project');
+ });
+
+ it('prefers the override if given', async () => {
+ const client = new WorkloadIdentityClient({
+ projectID: 'my-other-project',
+ providerID: 'my-provider',
+ token: 'my-token',
+ serviceAccount: 'my-service@my-project.iam.gserviceaccount.com',
+ audience: 'my-aud',
+ });
+
+ const result = await client.getProjectID();
+ expect(result).to.eq('my-other-project');
+ });
+
+ it('throws an error when extraction fails', async () => {
+ const fn = () => {
+ return new WorkloadIdentityClient({
+ providerID: 'my-provider',
+ token: 'my-token',
+ serviceAccount: 'my-service@developers.google.com',
+ audience: 'my-aud',
+ });
+ };
+ return expect(fn).to.throw(Error);
+ });
+ });
+
+ describe('#getServiceAccount', () => {
+ it('returns the provided value', async () => {
+ const client = new WorkloadIdentityClient({
+ projectID: 'my-project',
+ providerID: 'my-provider',
+ serviceAccount: 'my-service@my-project.iam.gserviceaccount.com',
+ token: 'my-token',
+ audience: 'my-aud',
+ });
+ const result = await client.getServiceAccount();
+ expect(result).to.eq('my-service@my-project.iam.gserviceaccount.com');
+ });
+ });
+
+ describe('#createCredentialsFile', () => {
+ it('writes the file', async () => {
+ process.env.ACTIONS_ID_TOKEN_REQUEST_URL = 'https://actions-token.url';
+ process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'github-token';
+
+ const tmp = tmpdir();
+ const client = new WorkloadIdentityClient({
+ projectID: 'my-project',
+ providerID: 'my-provider',
+ serviceAccount: 'my-service@my-project.iam.gserviceaccount.com',
+ token: 'my-token',
+ audience: 'my-aud',
+ });
+
+ const exp = {
+ audience: '//iam.googleapis.com/my-provider',
+ credential_source: {
+ format: {
+ subject_token_field_name: 'value',
+ type: 'json',
+ },
+ headers: {
+ Authorization: 'Bearer github-token',
+ },
+ url: 'https://actions-token.url/?audience=my-aud',
+ },
+ service_account_impersonation_url:
+ 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/my-service@my-project.iam.gserviceaccount.com:generateAccessToken',
+ subject_token_type: 'urn:ietf:params:oauth:token-type:jwt',
+ token_url: 'https://sts.googleapis.com/v1/token',
+ type: 'external_account',
+ };
+
+ const pth = await client.createCredentialsFile(tmp);
+ const data = readFileSync(pth);
+ const got = JSON.parse(data.toString('utf8'));
+
+ expect(got).to.deep.equal(exp);
+ });
+ });
+});