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:
parent
d5a354ef10
commit
2f0b4dbd9b
148
.github/workflows/test.yaml
vendored
148
.github/workflows/test.yaml
vendored
@ -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
|
||||
|
@ -2,7 +2,6 @@ module.exports = {
|
||||
arrowParens: 'always',
|
||||
bracketSpacing: true,
|
||||
endOfLine: 'auto',
|
||||
jsxBracketSameLine: true,
|
||||
jsxSingleQuote: true,
|
||||
printWidth: 100,
|
||||
quoteProps: 'consistent',
|
||||
|
168
README.md
168
README.md
@ -1,12 +1,15 @@
|
||||
# auth
|
||||
|
||||
This GitHub Action exchanges a GitHub Actions OIDC token into a Google Cloud
|
||||
access token using [Workload Identity Federation][wif]. This obviates the need
|
||||
This GitHub Action establishes authentication to Google Cloud. It supports
|
||||
traditional authentication via a Google Cloud Service Account Key JSON and
|
||||
authentication via [Workload Identity Federation][wif].
|
||||
|
||||
Workload Identity Federation is the recommended approach as it obviates the need
|
||||
to export a long-lived Google Cloud service account key and establishes a trust
|
||||
delegation relationship between a particular GitHub Actions workflow invocation
|
||||
and permissions on Google Cloud.
|
||||
|
||||
#### Previously
|
||||
#### With Service Account Key JSON
|
||||
|
||||
1. Create a Google Cloud service account and grant IAM permissions
|
||||
1. Export the long-lived JSON service account key
|
||||
@ -22,8 +25,12 @@ and permissions on Google Cloud.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- This action requires you to create and configure a Google Cloud Workload
|
||||
Identity Provider. See [#setup](#setup) for instructions.
|
||||
- For authenticating via Google Cloud Service Account Keys, you must create an
|
||||
export a Google Cloud Service Account Key in JSON format.
|
||||
|
||||
- For authenticating via Workload Identity Federation, you must create and
|
||||
configure a Google Cloud Workload Identity Provider. See [setup](#setup)
|
||||
for instructions.
|
||||
|
||||
|
||||
## Usage
|
||||
@ -62,17 +69,21 @@ See [Examples](#examples) for more examples.
|
||||
|
||||
## Inputs
|
||||
|
||||
- `workload_identity_provider`: (Required) The full identifier of the Workload
|
||||
Identity Provider, including the project number, pool name, and provider
|
||||
name. This must be the full identifier which includes all parts, for
|
||||
example:
|
||||
### Authenticating via Workload Identity Federation
|
||||
|
||||
The following inputs are for _authenticating_ to Google Cloud via Workload
|
||||
Identity Federation.
|
||||
|
||||
- `workload_identity_provider`: (Required) The full identifier of the Workload Identity
|
||||
Provider, including the project number, pool name, and provider name. If
|
||||
provided, this must be the full identifier which includes all parts:
|
||||
|
||||
```text
|
||||
projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider
|
||||
```
|
||||
|
||||
- `service_account`: (Required) Email address or unique identifier of the
|
||||
Google Cloud service account for which to generate credentials. For example:
|
||||
- `service_account`: (Required) Email address or unique identifier of the Google Cloud
|
||||
service account for which to generate credentials. For example:
|
||||
|
||||
```text
|
||||
my-service-account@my-project.iam.gserviceaccount.com
|
||||
@ -81,30 +92,29 @@ See [Examples](#examples) for more examples.
|
||||
- `audience`: (Optional) The value for the audience (`aud`) parameter in the
|
||||
generated GitHub Actions OIDC token. This value defaults to the value of
|
||||
`workload_identity_provider`, which is also the default value Google Cloud
|
||||
expects for the audience parameter on the token.
|
||||
expects for the audience parameter on the token. We do not recommend
|
||||
changing this value.
|
||||
|
||||
- `create_credentials_file`: (Optional) If true, the action will securely
|
||||
generate a credentials file which can be used for authentication via gcloud
|
||||
and Google Cloud SDKs. The default is false.
|
||||
### Authenticating via Service Account Key JSON
|
||||
|
||||
- `activate_credentials_file`: (Optional) If true and
|
||||
"create_credentials_file" is also true, this will set the
|
||||
`GOOGLE_APPLICATION_CREDENTIALS` environment variable to the path to the
|
||||
credentials file, which gcloud and Google Cloud SDKs automatically consume.
|
||||
The default value is true.
|
||||
The following inputs are for _authenticating_ to Google Cloud via a Service
|
||||
Account Key JSON. **We recommend using Workload Identity Federation instead as
|
||||
exporting a long-lived Service Account Key JSON credential poses a security
|
||||
risk.**
|
||||
|
||||
- `token_format`: (Optional) Output format for the generated authentication
|
||||
token.
|
||||
- `credentials_json`: (Required) The Google Cloud JSON service account key to
|
||||
use for authentication. To generate access tokens or ID tokens using this
|
||||
service account, you must grant the underlying service account
|
||||
`roles/iam.serviceAccountTokenCreator` permissions on itself.
|
||||
|
||||
- For OAuth 2.0 access tokens, specify "access_token".
|
||||
- For OIDC tokens, specify "id_token".
|
||||
- To skip token generation, omit or set to the empty string "".
|
||||
### Generating OAuth 2.0 access tokens
|
||||
|
||||
The default value is "" (skip token creation).
|
||||
The following inputs are for _generating_ OAuth 2.0 access tokens for
|
||||
authenticating to Google Cloud as an output for use in future steps in the
|
||||
workflow.
|
||||
|
||||
- `delegates`: (Optional) List of additional service account emails or unique
|
||||
identities to use for impersonation in the chain. By default there are no
|
||||
delegates.
|
||||
- `token_format`: This value must be `"access_token"` to generate OAuth 2.0
|
||||
access tokens. To skip token generation, omit or set to the empty string "".
|
||||
|
||||
- `access_token_lifetime`: (Optional) Desired lifetime duration of the access
|
||||
token, in seconds. This must be specified as the number of seconds with a
|
||||
@ -118,18 +128,43 @@ See [Examples](#examples) for more examples.
|
||||
https://www.googleapis.com/auth/cloud-platform
|
||||
```
|
||||
|
||||
- `id_token_audience`: (Required\*) The audience for the generated ID Token.
|
||||
This option is required when "token_format" is "id_token", but otherwise can
|
||||
be omitted.
|
||||
### Generating ID tokens
|
||||
|
||||
The following inputs are for _generating_ ID tokens for authenticating to Google
|
||||
Cloud as an output for use in future steps in the workflow.
|
||||
|
||||
- `token_format`: This value must be `"id_token"` to generate ID tokens. To
|
||||
skip token generation, omit or set to the empty string "".
|
||||
|
||||
- `id_token_audience`: (Required) The audience for the generated ID Token.
|
||||
|
||||
- `id_token_include_email`: (Optional) Optional parameter of whether to
|
||||
include the service account email in the generated token. If true, the token
|
||||
will contain "email" and "email_verified" claims. This is only valid when
|
||||
"token_format" is "id_token". The default value is false.
|
||||
|
||||
### Other inputs
|
||||
|
||||
The following inputs are for controlling the behavior of this GitHub Actions,
|
||||
regardless of the authentication mechanism.
|
||||
|
||||
- `project_id`: (Optional) Custom project ID to use for authentication and
|
||||
exporting into other steps. If unspecified, the project ID will be extracted
|
||||
from the Workload Identity Provider or the Service Account Key JSON.
|
||||
|
||||
- `create_credentials_file`: (Optional) If true, the action will securely
|
||||
generate a credentials file which can be used for authentication via gcloud
|
||||
and Google Cloud SDKs in other steps in the workflow. The default is true.
|
||||
|
||||
- `delegates`: (Optional) List of additional service account emails or unique
|
||||
identities to use for impersonation in the chain. By default there are no
|
||||
delegates.
|
||||
|
||||
|
||||
## Outputs
|
||||
|
||||
- `project_id`: Provided or extracted value for the Google Cloud project ID.
|
||||
|
||||
- `credentials_file_path`: Path on the local filesystem where the generated
|
||||
credentials file resides. This is only available if
|
||||
"create_credentials_file" was set to true.
|
||||
@ -145,10 +180,60 @@ See [Examples](#examples) for more examples.
|
||||
|
||||
## Examples
|
||||
|
||||
#### Cloud SDK (gcloud)
|
||||
### Authenticating via Workload Identity Federation
|
||||
|
||||
This example demonstrates authenticating via Workload Identity Federation. For
|
||||
more information on how to setup and configure Workload Identity Federation, see
|
||||
[#setup](#setup).
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
job_id:
|
||||
# ...
|
||||
|
||||
# Add "id-token" with the intended permissions.
|
||||
permissions:
|
||||
contents: 'read'
|
||||
id-token: 'write'
|
||||
|
||||
steps:
|
||||
- id: 'auth'
|
||||
name: 'Authenticate to Google Cloud'
|
||||
uses: `google-github-actions/auth@v0.3.1'
|
||||
with:
|
||||
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||
service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
|
||||
```
|
||||
|
||||
### Authenticating via Service Account Key JSON
|
||||
|
||||
This example demonstrates authenticating via a Google Cloud Service Account Key
|
||||
JSON. **We recommend using Workload Identity Federation instead as exporting a
|
||||
long-lived Service Account Key JSON credential poses a security risk.**
|
||||
|
||||
This example assumes you have created a GitHub Secret named 'GOOGLE_CREDENTIALS'
|
||||
with the contents being an export Google Cloud Service Account Key JSON. See
|
||||
[Creating and managing Google Cloud Service Account
|
||||
Keys](https://cloud.google.com/iam/docs/creating-managing-service-account-keys)
|
||||
for more information.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
job_id:
|
||||
# ...
|
||||
|
||||
steps:
|
||||
- id: 'auth'
|
||||
name: 'Authenticate to Google Cloud'
|
||||
uses: `google-github-actions/auth@v0.3.1'
|
||||
with:
|
||||
credentials_json: '${{ secrets.GOOGLE_CREDENTIALS }}'
|
||||
```
|
||||
|
||||
### Configuring gcloud
|
||||
|
||||
This example demonstrates using this GitHub Action to configure authentication
|
||||
for the `gcloud` CLI tool. Note this does **not** work for the `gsutil` tool.
|
||||
for the `gcloud` CLI tool. Note this does **NOT** work for the `gsutil` tool.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
@ -171,7 +256,6 @@ jobs:
|
||||
name: 'Authenticate to Google Cloud'
|
||||
uses: 'google-github-actions/auth@v0.3.1'
|
||||
with:
|
||||
create_credentials_file: 'true'
|
||||
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||
service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
|
||||
|
||||
@ -187,7 +271,7 @@ jobs:
|
||||
gcloud secrets versions access "latest" --secret "my-secret"
|
||||
```
|
||||
|
||||
#### Access Token (OAuth 2.0)
|
||||
### Generating an OAuth 2.0 Access Token
|
||||
|
||||
This example demonstrates using this GitHub Action to generate an OAuth 2.0
|
||||
Access Token for authenticating to Google Cloud. Most Google Cloud APIs accept
|
||||
@ -196,6 +280,9 @@ this access token as authentication.
|
||||
The default lifetime is 1 hour, but you can request up to 12 hours if you set
|
||||
the [`constraints/iam.allowServiceAccountCredentialLifetimeExtension` organization policy](https://cloud.google.com/resource-manager/docs/organization-policy/org-policy-constraints).
|
||||
|
||||
Note: If you authenticate via `credentials_json`, the service account must have
|
||||
`roles/iam.serviceAccountTokenCreator` on itself.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
job_id:
|
||||
@ -225,12 +312,15 @@ jobs:
|
||||
--header "Authorization: Bearer ${{ steps.auth.outputs.access_token }}"
|
||||
```
|
||||
|
||||
#### ID Token (JWT)
|
||||
### Generating an ID Token (JWT)
|
||||
|
||||
This example demonstrates using this GitHub Action to generate a Google Cloud ID
|
||||
Token for authenticating to Google Cloud. This is most commonly used when
|
||||
invoking a Cloud Run service.
|
||||
|
||||
Note: If you authenticate via `credentials_json`, the service account must have
|
||||
`roles/iam.serviceAccountTokenCreator` on itself.
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
job_id:
|
||||
@ -262,7 +352,9 @@ jobs:
|
||||
```
|
||||
|
||||
|
||||
## Setup
|
||||
<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
|
||||
|
41
action.yml
41
action.yml
@ -15,42 +15,48 @@
|
||||
name: 'Authenticate to Google Cloud'
|
||||
author: 'Google LLC'
|
||||
description: |-
|
||||
Generate credentials to authenticate to Google Cloud from GitHub Actions using
|
||||
an OIDC token and Workload Identity Federation.
|
||||
Authenticate to Google Cloud from GitHub Actions via Workload Identity
|
||||
Federation and GitHub Actions OIDC tokens or via exported Google Cloud service
|
||||
account keys.
|
||||
|
||||
inputs:
|
||||
project_id:
|
||||
description: |-
|
||||
ID of the default project to use for future API calls and invocations. If
|
||||
unspecified, this action will attempt to extract the value from other
|
||||
inputs such as "service_account" or "credentials_json".
|
||||
required: false
|
||||
workload_identity_provider:
|
||||
description: |-
|
||||
The full identifier of the Workload Identity Provider, including the
|
||||
project number, pool name, and provider name. This must be the full
|
||||
identifier which includes all parts, for example:
|
||||
project number, pool name, and provider name. If provided, this must be
|
||||
the full identifier which includes all parts, for example:
|
||||
"projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider".
|
||||
required: true
|
||||
This is mutually exclusive with "credentials_json".
|
||||
required: false
|
||||
service_account:
|
||||
description: |-
|
||||
Email address or unique identifier of the Google Cloud service account for
|
||||
which to generate credentials.
|
||||
required: true
|
||||
which to generate credentials. This is required if
|
||||
"workload_identity_provider" is specified.
|
||||
required: false
|
||||
audience:
|
||||
description: |-
|
||||
The value for the audience (aud) parameter in GitHub's generated OIDC
|
||||
token. This value defaults to the value of workload_identity_provider,
|
||||
token. This value defaults to the value of "workload_identity_provider",
|
||||
which is also the default value Google Cloud expects for the audience
|
||||
parameter on the token.
|
||||
default: ''
|
||||
required: false
|
||||
credentials_json:
|
||||
description: |-
|
||||
The Google Cloud JSON service account key to use for authentication. This
|
||||
is mutually exclusive with "workload_identity_provider".
|
||||
required: false
|
||||
create_credentials_file:
|
||||
description: |-
|
||||
If true, the action will securely generate a credentials file which can be
|
||||
used for authentication via gcloud and Google Cloud SDKs.
|
||||
default: false
|
||||
required: false
|
||||
activate_credentials_file:
|
||||
description: |-
|
||||
If true and create_credentials_file is also true, this will set the
|
||||
GOOGLE_APPLICATION_CREDENTIALS environment variable to the path to the
|
||||
credentials file, which gcloud and Google Cloud SDKs automatically
|
||||
consume.
|
||||
default: true
|
||||
required: false
|
||||
token_format:
|
||||
@ -99,6 +105,9 @@ inputs:
|
||||
required: false
|
||||
|
||||
outputs:
|
||||
project_id:
|
||||
description: |-
|
||||
Provided or extracted value for the Google Cloud project ID.
|
||||
credentials_file_path:
|
||||
description: |-
|
||||
Path on the local filesystem where the generated credentials file resides.
|
||||
|
571
dist/index.js
vendored
571
dist/index.js
vendored
@ -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
439
package-lock.json
generated
@ -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": {
|
||||
|
@ -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",
|
||||
|
@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
128
src/client/credentials_json_client.ts
Normal file
128
src/client/credentials_json_client.ts
Normal 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));
|
||||
}
|
||||
}
|
175
src/client/workload_identity_client.ts
Normal file
175
src/client/workload_identity_client.ts
Normal 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));
|
||||
}
|
||||
}
|
93
src/main.ts
93
src/main.ts
@ -1,8 +1,10 @@
|
||||
'use strict';
|
||||
|
||||
import * as core from '@actions/core';
|
||||
import { WIFClient } from './workload_identity';
|
||||
import { ActionAuth } from './actionauth';
|
||||
import { WorkloadIdentityClient } from './client/workload_identity_client';
|
||||
import { CredentialsJSONClient } from './client/credentials_json_client';
|
||||
import { AuthClient } from './client/auth_client';
|
||||
import { BaseClient } from './base';
|
||||
import { explodeStrings } from './utils';
|
||||
|
||||
/**
|
||||
@ -11,22 +13,53 @@ import { explodeStrings } from './utils';
|
||||
async function run(): Promise<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) {
|
||||
|
33
src/utils.ts
33
src/utils.ts
@ -3,14 +3,14 @@ import crypto from 'crypto';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* writeCredFile writes a file to disk in a given directory with a
|
||||
* writeSecureFile writes a file to disk in a given directory with a
|
||||
* random name.
|
||||
*
|
||||
* @param outputDir Directory to create random file in.
|
||||
* @param data Data to write to file.
|
||||
* @returns Path to written file.
|
||||
*/
|
||||
export async function writeCredFile(outputDir: string, data: string): Promise<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() : '';
|
||||
}
|
||||
|
@ -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 };
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
import { expect } from 'chai';
|
||||
import 'mocha';
|
||||
|
||||
describe('Client', () => {
|
||||
it('todo');
|
||||
});
|
107
tests/client/credentials_json_client.test.ts
Normal file
107
tests/client/credentials_json_client.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
102
tests/client/workload_identity_client.test.ts
Normal file
102
tests/client/workload_identity_client.test.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user