Add support for specifying authentication via JSON service account keys (#37)

* Add support for specifying authentication via JSON service account keys

* Update README.md

Co-authored-by: Bharath KKB <bharathkrishnakb@gmail.com>

* Update README.md

Co-authored-by: Bharath KKB <bharathkrishnakb@gmail.com>

* Update README.md

Co-authored-by: Bharath KKB <bharathkrishnakb@gmail.com>

* Review feedback

* Undo interface

* Use TokenCreator instead

Co-authored-by: Bharath KKB <bharathkrishnakb@gmail.com>
This commit is contained in:
Seth Vargo 2021-11-08 17:13:59 -05:00 committed by GitHub
parent d5a354ef10
commit 2f0b4dbd9b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1445 additions and 773 deletions

View File

@ -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

View File

@ -2,7 +2,6 @@ module.exports = {
arrowParens: 'always',
bracketSpacing: true,
endOfLine: 'auto',
jsxBracketSameLine: true,
jsxSingleQuote: true,
printWidth: 100,
quoteProps: 'consistent',

168
README.md
View File

@ -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
<a id="setup"></a>
## 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

View File

@ -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.

571
dist/index.js vendored
View File

@ -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:

439
package-lock.json generated
View File

@ -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": {

View File

@ -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",

View File

@ -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}`);
}
}
}

View File

@ -1,10 +1,11 @@
/**
* Defines the main interface for all clients that generate credentials.
*/
export interface ActionAuth {
getAccessToken(opts: GoogleAccessTokenParameters): Promise<GoogleAccessTokenResponse>;
getIDToken(opts: GoogleIDTokenParameters): Promise<GoogleIDTokenResponse>;
createCredentialsFile(outputDir: string): Promise<CreateCredentialsFileResponse>;
export interface AuthClient {
getAuthToken(): Promise<string>;
getProjectID(): Promise<string>;
getServiceAccount(): Promise<string>;
createCredentialsFile(outputDir: string): Promise<string>;
}
/**
@ -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<string, string>;
}

View File

@ -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<string, string>;
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<string, string> {
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<string, string>;
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<string> {
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<string> {
return this.#projectID;
}
/**
* getServiceAccount returns the service account email for the authentication,
* extracted from the Service Account Key JSON.
*/
async getServiceAccount(): Promise<string> {
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<string> {
return await writeSecureFile(outputDir, JSON.stringify(this.#credentials));
}
}

View File

@ -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<string> {
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<string> {
return this.#projectID;
}
/**
* getServiceAccount returns the service account email for the authentication,
* extracted from the input parameter.
*/
async getServiceAccount(): Promise<string> {
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<string> {
// 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));
}
}

View File

@ -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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
break;
}
default: {
throw new Error(`unknown token format "${tokenFormat}"`);
throw new Error(`Unknown token format "${tokenFormat}"`);
}
}
} catch (err) {

View File

@ -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<string> {
export async function writeSecureFile(outputDir: string, data: string): Promise<string> {
// 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<string> {
}
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() : '';
}

View File

@ -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<string> {
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<string> {
// 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<GoogleAccessTokenResponse> {
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<GoogleIDTokenResponse> {
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<CreateCredentialsFileResponse> {
// 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<string, string>([['GOOGLE_APPLICATION_CREDENTIALS', credentialsPath]]);
return { credentialsPath, envVars };
}
}

View File

@ -1,6 +0,0 @@
import { expect } from 'chai';
import 'mocha';
describe('Client', () => {
it('todo');
});

View File

@ -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);
});
});
});

View File

@ -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);
});
});
});