Add the ability to generate and export a credentials file (#7)
This credentials file can be passed to gcloud or other Google Cloud SDKs to automatically do the exchange.
This commit is contained in:
parent
c7bb6ad28f
commit
febe21311b
69
.github/workflows/test.yaml
vendored
69
.github/workflows/test.yaml
vendored
@ -16,7 +16,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: 'actions/checkout@v2'
|
- uses: 'actions/checkout@v2'
|
||||||
|
|
||||||
- uses: 'actions/setup-node@master'
|
- uses: 'actions/setup-node@v2'
|
||||||
with:
|
with:
|
||||||
node-version: '12.x'
|
node-version: '12.x'
|
||||||
|
|
||||||
@ -29,11 +29,11 @@ jobs:
|
|||||||
- name: 'npm test'
|
- name: 'npm test'
|
||||||
run: 'npm run test'
|
run: 'npm run test'
|
||||||
|
|
||||||
access_token:
|
credentials_file:
|
||||||
name: 'access_token'
|
name: 'credentials_file'
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: 'write'
|
||||||
contents: read
|
contents: 'read'
|
||||||
runs-on: '${{ matrix.operating-system }}'
|
runs-on: '${{ matrix.operating-system }}'
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -42,15 +42,50 @@ jobs:
|
|||||||
- 'ubuntu-latest'
|
- 'ubuntu-latest'
|
||||||
- 'windows-latest'
|
- 'windows-latest'
|
||||||
- 'macos-latest'
|
- 'macos-latest'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: 'actions/checkout@v2'
|
- uses: 'actions/checkout@v2'
|
||||||
|
|
||||||
- uses: 'actions/setup-node@master'
|
- uses: 'actions/setup-node@v2'
|
||||||
with:
|
with:
|
||||||
node-version: '12.x'
|
node-version: '12.x'
|
||||||
|
|
||||||
- id: 'access-token'
|
- uses: 'google-github-actions/setup-gcloud@master'
|
||||||
name: 'integration'
|
with:
|
||||||
|
project_id: 'actions-oidc-test'
|
||||||
|
|
||||||
|
- id: 'auth'
|
||||||
|
name: 'auth'
|
||||||
|
uses: './'
|
||||||
|
with:
|
||||||
|
create_credentials_file: true
|
||||||
|
workload_identity_provider: 'projects/469401941463/locations/global/workloadIdentityPools/github-actions/providers/github-oidc-auth-google-cloud'
|
||||||
|
service_account: 'github-secret-accessor@actions-oidc-test.iam.gserviceaccount.com'
|
||||||
|
|
||||||
|
- id: 'gcloud'
|
||||||
|
name: 'gcloud'
|
||||||
|
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'
|
||||||
|
|
||||||
|
- id: 'auth'
|
||||||
|
name: 'auth'
|
||||||
uses: './'
|
uses: './'
|
||||||
with:
|
with:
|
||||||
token_format: 'access_token'
|
token_format: 'access_token'
|
||||||
@ -60,25 +95,21 @@ jobs:
|
|||||||
id_token:
|
id_token:
|
||||||
name: 'id_token'
|
name: 'id_token'
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: 'write'
|
||||||
contents: read
|
contents: 'read'
|
||||||
runs-on: '${{ matrix.operating-system }}'
|
runs-on: 'ubuntu-latest'
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
|
||||||
operating-system:
|
|
||||||
- 'ubuntu-latest'
|
|
||||||
- 'windows-latest'
|
|
||||||
- 'macos-latest'
|
|
||||||
steps:
|
steps:
|
||||||
- uses: 'actions/checkout@v2'
|
- uses: 'actions/checkout@v2'
|
||||||
|
|
||||||
- uses: 'actions/setup-node@master'
|
- uses: 'actions/setup-node@v2'
|
||||||
with:
|
with:
|
||||||
node-version: '12.x'
|
node-version: '12.x'
|
||||||
|
|
||||||
- id: 'id-token'
|
- id: 'auth'
|
||||||
name: 'integration'
|
name: 'auth'
|
||||||
uses: './'
|
uses: './'
|
||||||
with:
|
with:
|
||||||
token_format: 'id_token'
|
token_format: 'id_token'
|
||||||
|
172
README.md
172
README.md
@ -19,11 +19,13 @@ and permissions on Google Cloud.
|
|||||||
1. Exchange the GitHub Actions OIDC token for a short-lived Google Cloud access
|
1. Exchange the GitHub Actions OIDC token for a short-lived Google Cloud access
|
||||||
token
|
token
|
||||||
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
- This action requires you to create and configure a Google Cloud Workload
|
- This action requires you to create and configure a Google Cloud Workload
|
||||||
Identity Provider. See [#setup](#setup) for instructions.
|
Identity Provider. See [#setup](#setup) for instructions.
|
||||||
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@ -33,11 +35,10 @@ jobs:
|
|||||||
|
|
||||||
# Add "id-token" with the intended permissions.
|
# Add "id-token" with the intended permissions.
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: 'write'
|
||||||
contents: read
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- id: 'google-cloud-auth'
|
- id: 'auth'
|
||||||
name: 'Authenticate to Google Cloud'
|
name: 'Authenticate to Google Cloud'
|
||||||
uses: 'sethvargo/oidc-auth-google-cloud@v0.2.0'
|
uses: 'sethvargo/oidc-auth-google-cloud@v0.2.0'
|
||||||
with:
|
with:
|
||||||
@ -45,13 +46,16 @@ jobs:
|
|||||||
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||||
service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
|
service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
|
||||||
|
|
||||||
# Example of using the output:
|
# Example of using the token:
|
||||||
- id: 'access-secret'
|
- name: 'Access secret'
|
||||||
run: |-
|
run: |-
|
||||||
curl https://secretmanager.googleapis.com/v1/projects/my-project/secrets/my-secret/versions/1:access \
|
curl https://secretmanager.googleapis.com/v1/projects/my-project/secrets/my-secret/versions/1:access \
|
||||||
--header "Authorization: Bearer ${{ steps.google-cloud-auth.outputs.access_token }}"
|
--header "Authorization: Bearer ${{ steps.auth.outputs.access_token }}"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
See [Examples](#examples) for more examples.
|
||||||
|
|
||||||
|
|
||||||
## Inputs
|
## Inputs
|
||||||
|
|
||||||
- `workload_identity_provider`: (Required) The full identifier of the Workload
|
- `workload_identity_provider`: (Required) The full identifier of the Workload
|
||||||
@ -75,9 +79,24 @@ jobs:
|
|||||||
`"sigstore"`, but this variable exists in case custom values are permitted
|
`"sigstore"`, but this variable exists in case custom values are permitted
|
||||||
in the future. The default value is `"sigstore"`.
|
in the future. The default value is `"sigstore"`.
|
||||||
|
|
||||||
- `token_format`: (Optional) Format of the generated token. For OAuth 2.0
|
- `create_credentials_file`: (Optional) If true, the action will securely
|
||||||
access tokens, specify "access_token". For OIDC tokens, specify "id_token".
|
generate a credentials file which can be used for authentication via gcloud
|
||||||
The default value is "access_token".
|
and Google Cloud SDKs. The default is false.
|
||||||
|
|
||||||
|
- `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.
|
||||||
|
|
||||||
|
- `token_format`: (Optional) Output format for the generated authentication
|
||||||
|
token.
|
||||||
|
|
||||||
|
- 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 "".
|
||||||
|
|
||||||
|
The default value is "" (skip token creation).
|
||||||
|
|
||||||
- `delegates`: (Optional) List of additional service account emails or unique
|
- `delegates`: (Optional) List of additional service account emails or unique
|
||||||
identities to use for impersonation in the chain. By default there are no
|
identities to use for impersonation in the chain. By default there are no
|
||||||
@ -95,23 +114,144 @@ jobs:
|
|||||||
https://www.googleapis.com/auth/cloud-platform
|
https://www.googleapis.com/auth/cloud-platform
|
||||||
```
|
```
|
||||||
|
|
||||||
- `id_token_audience`: (Optional) The audience for the generated ID Token.
|
- `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.
|
||||||
|
|
||||||
- `id_token_include_email`: (Optional) Optional parameter of whether to
|
- `id_token_include_email`: (Optional) Optional parameter of whether to
|
||||||
include the service account email in the generated token. If true, the token
|
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
|
will contain "email" and "email_verified" claims. This is only valid when
|
||||||
"token_format" is "access_token". The default value is false.
|
"token_format" is "access_token". The default value is false.
|
||||||
|
|
||||||
|
|
||||||
## Outputs
|
## Outputs
|
||||||
|
|
||||||
- `access_token`: The authenticated Google Cloud access token for calling
|
- `credentials_file_path`: Path on the local filesystem where the generated
|
||||||
other Google Cloud APIs.
|
credentials file resides. This is only available if
|
||||||
|
"create_credentials_file" was set to true.
|
||||||
|
|
||||||
- `access_token_expiration`: The RFC3339 UTC "Zulu" format timestamp when the
|
- `access_token`: The Google Cloud access token for calling other Google Cloud
|
||||||
token expires.
|
APIs. This is only available when "token_format" is "access_token".
|
||||||
|
|
||||||
|
- `access_token_expiration`: The RFC3339 UTC "Zulu" format timestamp for the
|
||||||
|
access token. This is only available when "token_format" is "access_token".
|
||||||
|
|
||||||
|
- `id_token`: The Google Cloud ID token. This is only available when
|
||||||
|
"token_format" is "id_token".
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
#### Cloud SDK (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.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
run:
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# Add "id-token" with the intended permissions.
|
||||||
|
permissions:
|
||||||
|
id-token: 'write'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Install gcloud, do not specify authentication.
|
||||||
|
- uses: 'google-github-actions/setup-gcloud@master'
|
||||||
|
with:
|
||||||
|
project_id: 'my-project'
|
||||||
|
|
||||||
|
# Configure Workload Identity Federation via a credentials file.
|
||||||
|
- id: 'auth'
|
||||||
|
name: 'Authenticate to Google Cloud'
|
||||||
|
uses: 'sethvargo/oidc-auth-google-cloud@v0.2.0'
|
||||||
|
with:
|
||||||
|
create_credentials_file: 'access_token'
|
||||||
|
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||||
|
service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
|
||||||
|
|
||||||
|
# Authenticate using the created credentials file.
|
||||||
|
- id: 'gcloud'
|
||||||
|
name: 'gcloud'
|
||||||
|
run: |-
|
||||||
|
gcloud auth login --brief --cred-file="${{ steps.auth.outputs.credentials_file_path }}"
|
||||||
|
|
||||||
|
# Now you can run gcloud commands authenticated as the impersonated service account.
|
||||||
|
gcloud secrets versions access "latest" --secret "my-secret"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Access Token (OAuth 2.0)
|
||||||
|
|
||||||
|
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
|
||||||
|
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).
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
run:
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# Add "id-token" with the intended permissions.
|
||||||
|
permissions:
|
||||||
|
id-token: 'write'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Configure Workload Identity Federation and generate an access token.
|
||||||
|
- id: 'auth'
|
||||||
|
name: 'Authenticate to Google Cloud'
|
||||||
|
uses: 'sethvargo/oidc-auth-google-cloud@v0.2.0'
|
||||||
|
with:
|
||||||
|
token_format: 'access_token'
|
||||||
|
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||||
|
service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
|
||||||
|
access_token_lifetime: '300s' # optional, default: '3600s' (1 hour)
|
||||||
|
|
||||||
|
# Example of using the output. The token is usually provided as a Bearer
|
||||||
|
# token.
|
||||||
|
- id: 'access-secret'
|
||||||
|
run: |-
|
||||||
|
curl https://secretmanager.googleapis.com/v1/projects/my-project/secrets/my-secret/versions/1:access \
|
||||||
|
--header "Authorization: Bearer ${{ steps.auth.outputs.access_token }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 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.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
run:
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# Add "id-token" with the intended permissions.
|
||||||
|
permissions:
|
||||||
|
id-token: 'write'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
# Configure Workload Identity Federation and generate an access token.
|
||||||
|
- id: 'auth'
|
||||||
|
name: 'Authenticate to Google Cloud'
|
||||||
|
uses: 'sethvargo/oidc-auth-google-cloud@v0.2.0'
|
||||||
|
with:
|
||||||
|
token_format: 'access_token'
|
||||||
|
workload_identity_provider: 'projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider'
|
||||||
|
service_account: 'my-service-account@my-project.iam.gserviceaccount.com'
|
||||||
|
id_token_audience: 'https://myapp-uvehjacqzq.a.run.app' # required, value depends on target
|
||||||
|
id_token_include_email: true # optional
|
||||||
|
|
||||||
|
# Example of using the output. The token is usually provided as a Bearer
|
||||||
|
# token.
|
||||||
|
- id: 'invoke-service'
|
||||||
|
run: |-
|
||||||
|
curl https://myapp-uvehjacqzq.a.run.app \
|
||||||
|
--header "Authorization: Bearer ${{ steps.auth.outputs.id_token }}"
|
||||||
|
```
|
||||||
|
|
||||||
- `id_token`: The authenticated Google Cloud ID token. This token is only
|
|
||||||
generated when `id_token_audience` input parameter is provided.
|
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
|
37
action.yml
37
action.yml
@ -38,12 +38,27 @@ inputs:
|
|||||||
exists in case custom values are permitted in the future.
|
exists in case custom values are permitted in the future.
|
||||||
default: 'sigstore'
|
default: 'sigstore'
|
||||||
required: false
|
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:
|
token_format:
|
||||||
description: |-
|
description: |-
|
||||||
Format for the generated token. For OAuth 2.0 access tokens, specify
|
Output format for the generated authentication token. For OAuth 2.0 access
|
||||||
"access_token". For OIDC tokens, specify "id_token".
|
tokens, specify "access_token". For OIDC tokens, specify "id_token". To
|
||||||
default: 'access_token'
|
skip token generation, leave this value empty.
|
||||||
required: true
|
default: ''
|
||||||
|
required: false
|
||||||
delegates:
|
delegates:
|
||||||
description: |-
|
description: |-
|
||||||
List of additional service account emails or unique identities to use for
|
List of additional service account emails or unique identities to use for
|
||||||
@ -78,19 +93,23 @@ inputs:
|
|||||||
Optional parameter of whether to include the service account email in the
|
Optional parameter of whether to include the service account email in the
|
||||||
generated token. If true, the token will contain "email" and
|
generated token. If true, the token will contain "email" and
|
||||||
"email_verified" claims. This is only valid when "token_format" is
|
"email_verified" claims. This is only valid when "token_format" is
|
||||||
"access_token".
|
"id_token".
|
||||||
default: false
|
default: false
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
outputs:
|
outputs:
|
||||||
|
credentials_file_path:
|
||||||
|
description: |-
|
||||||
|
Path on the local filesystem where the generated credentials file resides.
|
||||||
|
This is only available if "create_credentials_file" was set to true.
|
||||||
access_token:
|
access_token:
|
||||||
description: |-
|
description: |-
|
||||||
The Google Cloud access token for calling other Google Cloud APIs. This
|
The Google Cloud access token for calling other Google Cloud APIs. This is
|
||||||
is only available when "token_format" is "access_token".
|
only available when "token_format" is "access_token".
|
||||||
access_token_expiration:
|
access_token_expiration:
|
||||||
description: |-
|
description: |-
|
||||||
The expiration timestamp for the access token. This is only available
|
The RFC3339 UTC "Zulu" format timestamp for the access token. This is only
|
||||||
when "token_format" is "access_token".
|
available when "token_format" is "access_token".
|
||||||
id_token:
|
id_token:
|
||||||
description: |-
|
description: |-
|
||||||
The Google Cloud ID token. This is only available when "token_format" is
|
The Google Cloud ID token. This is only available when "token_format" is
|
||||||
|
129
dist/index.js
vendored
129
dist/index.js
vendored
@ -194,6 +194,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const core = __importStar(__webpack_require__(470));
|
const core = __importStar(__webpack_require__(470));
|
||||||
const client_1 = __webpack_require__(976);
|
const client_1 = __webpack_require__(976);
|
||||||
|
const url_1 = __webpack_require__(835);
|
||||||
/**
|
/**
|
||||||
* Converts a multi-line or comma-separated collection of strings into an array
|
* Converts a multi-line or comma-separated collection of strings into an array
|
||||||
* of trimmed strings.
|
* of trimmed strings.
|
||||||
@ -225,23 +226,70 @@ function run() {
|
|||||||
});
|
});
|
||||||
const serviceAccount = core.getInput('service_account', { required: true });
|
const serviceAccount = core.getInput('service_account', { required: true });
|
||||||
const audience = core.getInput('audience');
|
const audience = core.getInput('audience');
|
||||||
const tokenFormat = core.getInput('token_format', { required: true });
|
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 delegates = explodeStrings(core.getInput('delegates'));
|
||||||
const accessTokenLifetime = core.getInput('access_token_lifetime');
|
// Always write the credentials file first, before trying to generate
|
||||||
const accessTokenScopes = explodeStrings(core.getInput('access_token_scopes'));
|
// tokens. This will ensure the file is written even if token generation
|
||||||
const idTokenAudience = core.getInput('id_token_audience');
|
// fails, which means continue-on-error actions will still have the file
|
||||||
const idTokenIncludeEmail = core.getBooleanInput('id_token_include_email');
|
// available.
|
||||||
// Get the GitHub OIDC token.
|
if (createCredentialsFile) {
|
||||||
const githubOIDCToken = yield core.getIDToken(audience);
|
const runnerTempDir = process.env.RUNNER_TEMP;
|
||||||
// Exchange the GitHub OIDC token for a Google Federated Token.
|
// Extract the request token and request URL from the environment. These
|
||||||
const googleFederatedToken = yield client_1.Client.googleFederatedToken({
|
// are only set when an id-token is requested and the submitter has
|
||||||
providerID: workloadIdentityProvider,
|
// collaborator permissions.
|
||||||
token: githubOIDCToken,
|
const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
||||||
|
if (!requestToken) {
|
||||||
|
throw new Error('$ACTIONS_ID_TOKEN_REQUEST_TOKEN is not set');
|
||||||
|
}
|
||||||
|
const requestURLRaw = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
||||||
|
if (!requestURLRaw) {
|
||||||
|
throw new Error('$ACTIONS_ID_TOKEN_REQUEST_URL is not set');
|
||||||
|
}
|
||||||
|
const requestURL = new url_1.URL(requestURLRaw);
|
||||||
|
// Append the audience value to the request.
|
||||||
|
const params = requestURL.searchParams;
|
||||||
|
params.set('audience', audience);
|
||||||
|
requestURL.search = params.toString();
|
||||||
|
// Create the credentials file.
|
||||||
|
const outputPath = yield client_1.Client.createCredentialsFile({
|
||||||
|
providerID: workloadIdentityProvider,
|
||||||
|
serviceAccount: serviceAccount,
|
||||||
|
requestToken: requestToken,
|
||||||
|
requestURL: requestURL.toString(),
|
||||||
|
outputDir: runnerTempDir,
|
||||||
|
});
|
||||||
|
core.setOutput('credentials_file_path', outputPath);
|
||||||
|
// Also set the magic environment variable for gcloud and SDKs if
|
||||||
|
// requested.
|
||||||
|
if (activateCredentialsFile) {
|
||||||
|
core.exportVariable('GOOGLE_APPLICATION_CREDENTIALS', outputPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// getFederatedToken is a closure that gets the federated token.
|
||||||
|
const getFederatedToken = () => __awaiter(this, void 0, void 0, function* () {
|
||||||
|
// Get the GitHub OIDC token.
|
||||||
|
const githubOIDCToken = yield core.getIDToken(audience);
|
||||||
|
// Exchange the GitHub OIDC token for a Google Federated Token.
|
||||||
|
const googleFederatedToken = yield client_1.Client.googleFederatedToken({
|
||||||
|
providerID: workloadIdentityProvider,
|
||||||
|
token: githubOIDCToken,
|
||||||
|
});
|
||||||
|
core.setSecret(googleFederatedToken);
|
||||||
|
return googleFederatedToken;
|
||||||
});
|
});
|
||||||
core.setSecret(googleFederatedToken);
|
|
||||||
switch (tokenFormat) {
|
switch (tokenFormat) {
|
||||||
|
case '': {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case null: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'access_token': {
|
case 'access_token': {
|
||||||
// Exchange the Google Federated Token for an access token.
|
const accessTokenLifetime = core.getInput('access_token_lifetime');
|
||||||
|
const accessTokenScopes = explodeStrings(core.getInput('access_token_scopes'));
|
||||||
|
const googleFederatedToken = yield getFederatedToken();
|
||||||
const { accessToken, expiration } = yield client_1.Client.googleAccessToken({
|
const { accessToken, expiration } = yield client_1.Client.googleAccessToken({
|
||||||
token: googleFederatedToken,
|
token: googleFederatedToken,
|
||||||
serviceAccount: serviceAccount,
|
serviceAccount: serviceAccount,
|
||||||
@ -255,7 +303,9 @@ function run() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'id_token': {
|
case 'id_token': {
|
||||||
// Exchange the Google Federated Token for an id token.
|
const idTokenAudience = core.getInput('id_token_audience', { required: true });
|
||||||
|
const idTokenIncludeEmail = core.getBooleanInput('id_token_include_email');
|
||||||
|
const googleFederatedToken = yield getFederatedToken();
|
||||||
const { token } = yield client_1.Client.googleIDToken({
|
const { token } = yield client_1.Client.googleIDToken({
|
||||||
token: googleFederatedToken,
|
token: googleFederatedToken,
|
||||||
serviceAccount: serviceAccount,
|
serviceAccount: serviceAccount,
|
||||||
@ -640,6 +690,13 @@ module.exports = require("assert");
|
|||||||
module.exports = __webpack_require__(141);
|
module.exports = __webpack_require__(141);
|
||||||
|
|
||||||
|
|
||||||
|
/***/ }),
|
||||||
|
|
||||||
|
/***/ 417:
|
||||||
|
/***/ (function(module) {
|
||||||
|
|
||||||
|
module.exports = require("crypto");
|
||||||
|
|
||||||
/***/ }),
|
/***/ }),
|
||||||
|
|
||||||
/***/ 431:
|
/***/ 431:
|
||||||
@ -1823,6 +1880,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.Client = void 0;
|
exports.Client = void 0;
|
||||||
const https_1 = __importDefault(__webpack_require__(211));
|
const https_1 = __importDefault(__webpack_require__(211));
|
||||||
|
const fs_1 = __webpack_require__(747);
|
||||||
|
const crypto_1 = __importDefault(__webpack_require__(417));
|
||||||
|
const path_1 = __importDefault(__webpack_require__(622));
|
||||||
const url_1 = __webpack_require__(835);
|
const url_1 = __webpack_require__(835);
|
||||||
class Client {
|
class Client {
|
||||||
/**
|
/**
|
||||||
@ -1830,6 +1890,12 @@ class Client {
|
|||||||
* request.
|
* request.
|
||||||
*/
|
*/
|
||||||
static request(opts, data) {
|
static request(opts, data) {
|
||||||
|
if (!opts.headers) {
|
||||||
|
opts.headers = {};
|
||||||
|
}
|
||||||
|
if (!opts.headers['User-Agent']) {
|
||||||
|
opts.headers['User-Agent'] = 'sethvargo:oidc-auth-google-cloud/0.2.1';
|
||||||
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const req = https_1.default.request(opts, (res) => {
|
const req = https_1.default.request(opts, (res) => {
|
||||||
res.setEncoding('utf8');
|
res.setEncoding('utf8');
|
||||||
@ -1963,6 +2029,41 @@ class Client {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* createCredentialsFile creates a Google Cloud credentials file that can be
|
||||||
|
* set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries.
|
||||||
|
*/
|
||||||
|
static createCredentialsFile({ providerID, serviceAccount, requestToken, requestURL, outputDir, }) {
|
||||||
|
return __awaiter(this, void 0, void 0, function* () {
|
||||||
|
const data = {
|
||||||
|
type: 'external_account',
|
||||||
|
audience: `//iam.googleapis.com/${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/${serviceAccount}:generateAccessToken`,
|
||||||
|
credential_source: {
|
||||||
|
url: requestURL,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${requestToken}`,
|
||||||
|
},
|
||||||
|
format: {
|
||||||
|
type: 'json',
|
||||||
|
subject_token_field_name: 'value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
// 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
|
||||||
|
// entire leading path).
|
||||||
|
const uniqueName = crypto_1.default.randomBytes(12).toString('hex');
|
||||||
|
const pth = path_1.default.join(outputDir, uniqueName);
|
||||||
|
// Write the file as 0640 so the owner has RW, group as R, and the file is
|
||||||
|
// otherwise unreadable. Also write with EXCL to prevent a symlink attack.
|
||||||
|
yield fs_1.promises.writeFile(pth, JSON.stringify(data), { mode: 0o640, flag: 'wx' });
|
||||||
|
return pth;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
exports.Client = Client;
|
exports.Client = Client;
|
||||||
|
|
||||||
|
160
package-lock.json
generated
160
package-lock.json
generated
@ -269,9 +269,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/chai": {
|
"node_modules/@types/chai": {
|
||||||
"version": "4.2.21",
|
"version": "4.2.22",
|
||||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.21.tgz",
|
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz",
|
||||||
"integrity": "sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==",
|
"integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/json-schema": {
|
"node_modules/@types/json-schema": {
|
||||||
@ -287,19 +287,19 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "16.9.2",
|
"version": "16.9.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.6.tgz",
|
||||||
"integrity": "sha512-ZHty/hKoOLZvSz6BtP1g7tc7nUeJhoCf3flLjh8ZEv1vFKBWHXcnMbJMyN/pftSljNyy0kNW/UqI3DccnBnZ8w==",
|
"integrity": "sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.2.tgz",
|
||||||
"integrity": "sha512-UDqhWmd5i0TvPLmbK5xY3UZB0zEGseF+DHPghZ37Sb83Qd3p8ujhvAtkU4OF46Ka5Pm5kWvFIx0cCTBFKo0alA==",
|
"integrity": "sha512-w63SCQ4bIwWN/+3FxzpnWrDjQRXVEGiTt9tJTRptRXeFvdZc/wLiz3FQUwNQ2CVoRGI6KUWMNUj/pk63noUfcA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/experimental-utils": "4.31.1",
|
"@typescript-eslint/experimental-utils": "4.31.2",
|
||||||
"@typescript-eslint/scope-manager": "4.31.1",
|
"@typescript-eslint/scope-manager": "4.31.2",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
"functional-red-black-tree": "^1.0.1",
|
"functional-red-black-tree": "^1.0.1",
|
||||||
"regexpp": "^3.1.0",
|
"regexpp": "^3.1.0",
|
||||||
@ -324,15 +324,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/experimental-utils": {
|
"node_modules/@typescript-eslint/experimental-utils": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.2.tgz",
|
||||||
"integrity": "sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==",
|
"integrity": "sha512-3tm2T4nyA970yQ6R3JZV9l0yilE2FedYg8dcXrTar34zC9r6JB7WyBQbpIVongKPlhEMjhQ01qkwrzWy38Bk1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/json-schema": "^7.0.7",
|
"@types/json-schema": "^7.0.7",
|
||||||
"@typescript-eslint/scope-manager": "4.31.1",
|
"@typescript-eslint/scope-manager": "4.31.2",
|
||||||
"@typescript-eslint/types": "4.31.1",
|
"@typescript-eslint/types": "4.31.2",
|
||||||
"@typescript-eslint/typescript-estree": "4.31.1",
|
"@typescript-eslint/typescript-estree": "4.31.2",
|
||||||
"eslint-scope": "^5.1.1",
|
"eslint-scope": "^5.1.1",
|
||||||
"eslint-utils": "^3.0.0"
|
"eslint-utils": "^3.0.0"
|
||||||
},
|
},
|
||||||
@ -348,14 +348,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/parser": {
|
"node_modules/@typescript-eslint/parser": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.2.tgz",
|
||||||
"integrity": "sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ==",
|
"integrity": "sha512-EcdO0E7M/sv23S/rLvenHkb58l3XhuSZzKf6DBvLgHqOYdL6YFMYVtreGFWirxaU2mS1GYDby3Lyxco7X5+Vjw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/scope-manager": "4.31.1",
|
"@typescript-eslint/scope-manager": "4.31.2",
|
||||||
"@typescript-eslint/types": "4.31.1",
|
"@typescript-eslint/types": "4.31.2",
|
||||||
"@typescript-eslint/typescript-estree": "4.31.1",
|
"@typescript-eslint/typescript-estree": "4.31.2",
|
||||||
"debug": "^4.3.1"
|
"debug": "^4.3.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -375,13 +375,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/scope-manager": {
|
"node_modules/@typescript-eslint/scope-manager": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.2.tgz",
|
||||||
"integrity": "sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==",
|
"integrity": "sha512-2JGwudpFoR/3Czq6mPpE8zBPYdHWFGL6lUNIGolbKQeSNv4EAiHaR5GVDQaLA0FwgcdcMtRk+SBJbFGL7+La5w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "4.31.1",
|
"@typescript-eslint/types": "4.31.2",
|
||||||
"@typescript-eslint/visitor-keys": "4.31.1"
|
"@typescript-eslint/visitor-keys": "4.31.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
|
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
|
||||||
@ -392,9 +392,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/types": {
|
"node_modules/@typescript-eslint/types": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.2.tgz",
|
||||||
"integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==",
|
"integrity": "sha512-kWiTTBCTKEdBGrZKwFvOlGNcAsKGJSBc8xLvSjSppFO88AqGxGNYtF36EuEYG6XZ9vT0xX8RNiHbQUKglbSi1w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
|
"node": "^8.10.0 || ^10.13.0 || >=11.10.1"
|
||||||
@ -405,13 +405,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/typescript-estree": {
|
"node_modules/@typescript-eslint/typescript-estree": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.2.tgz",
|
||||||
"integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==",
|
"integrity": "sha512-ieBq8U9at6PvaC7/Z6oe8D3czeW5d//Fo1xkF/s9394VR0bg/UaMYPdARiWyKX+lLEjY3w/FNZJxitMsiWv+wA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "4.31.1",
|
"@typescript-eslint/types": "4.31.2",
|
||||||
"@typescript-eslint/visitor-keys": "4.31.1",
|
"@typescript-eslint/visitor-keys": "4.31.2",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
"globby": "^11.0.3",
|
"globby": "^11.0.3",
|
||||||
"is-glob": "^4.0.1",
|
"is-glob": "^4.0.1",
|
||||||
@ -432,12 +432,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@typescript-eslint/visitor-keys": {
|
"node_modules/@typescript-eslint/visitor-keys": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.2.tgz",
|
||||||
"integrity": "sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==",
|
"integrity": "sha512-PrBId7EQq2Nibns7dd/ch6S6/M4/iwLM9McbgeEbCXfxdwRUNxJ4UNreJ6Gh3fI2GNKNrWnQxKL7oCPmngKBug==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@typescript-eslint/types": "4.31.1",
|
"@typescript-eslint/types": "4.31.2",
|
||||||
"eslint-visitor-keys": "^2.0.0"
|
"eslint-visitor-keys": "^2.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -2888,9 +2888,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/chai": {
|
"@types/chai": {
|
||||||
"version": "4.2.21",
|
"version": "4.2.22",
|
||||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.21.tgz",
|
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.22.tgz",
|
||||||
"integrity": "sha512-yd+9qKmJxm496BOV9CMNaey8TWsikaZOwMRwPHQIjcOJM9oV+fi9ZMNw3JsVnbEEbo2gRTDnGEBv8pjyn67hNg==",
|
"integrity": "sha512-tFfcE+DSTzWAgifkjik9AySNqIyNoYwmR+uecPwwD/XRNfvOjmC/FjCxpiUGDkDVDphPfCUecSQVFw+lN3M3kQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/json-schema": {
|
"@types/json-schema": {
|
||||||
@ -2906,19 +2906,19 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "16.9.2",
|
"version": "16.9.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.6.tgz",
|
||||||
"integrity": "sha512-ZHty/hKoOLZvSz6BtP1g7tc7nUeJhoCf3flLjh8ZEv1vFKBWHXcnMbJMyN/pftSljNyy0kNW/UqI3DccnBnZ8w==",
|
"integrity": "sha512-YHUZhBOMTM3mjFkXVcK+WwAcYmyhe1wL4lfqNtzI0b3qAy7yuSetnM7QJazgE5PFmgVTNGiLOgRFfJMqW7XpSQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@typescript-eslint/eslint-plugin": {
|
"@typescript-eslint/eslint-plugin": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.31.2.tgz",
|
||||||
"integrity": "sha512-UDqhWmd5i0TvPLmbK5xY3UZB0zEGseF+DHPghZ37Sb83Qd3p8ujhvAtkU4OF46Ka5Pm5kWvFIx0cCTBFKo0alA==",
|
"integrity": "sha512-w63SCQ4bIwWN/+3FxzpnWrDjQRXVEGiTt9tJTRptRXeFvdZc/wLiz3FQUwNQ2CVoRGI6KUWMNUj/pk63noUfcA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/experimental-utils": "4.31.1",
|
"@typescript-eslint/experimental-utils": "4.31.2",
|
||||||
"@typescript-eslint/scope-manager": "4.31.1",
|
"@typescript-eslint/scope-manager": "4.31.2",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
"functional-red-black-tree": "^1.0.1",
|
"functional-red-black-tree": "^1.0.1",
|
||||||
"regexpp": "^3.1.0",
|
"regexpp": "^3.1.0",
|
||||||
@ -2927,55 +2927,55 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/experimental-utils": {
|
"@typescript-eslint/experimental-utils": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.31.2.tgz",
|
||||||
"integrity": "sha512-NtoPsqmcSsWty0mcL5nTZXMf7Ei0Xr2MT8jWjXMVgRK0/1qeQ2jZzLFUh4QtyJ4+/lPUyMw5cSfeeME+Zrtp9Q==",
|
"integrity": "sha512-3tm2T4nyA970yQ6R3JZV9l0yilE2FedYg8dcXrTar34zC9r6JB7WyBQbpIVongKPlhEMjhQ01qkwrzWy38Bk1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/json-schema": "^7.0.7",
|
"@types/json-schema": "^7.0.7",
|
||||||
"@typescript-eslint/scope-manager": "4.31.1",
|
"@typescript-eslint/scope-manager": "4.31.2",
|
||||||
"@typescript-eslint/types": "4.31.1",
|
"@typescript-eslint/types": "4.31.2",
|
||||||
"@typescript-eslint/typescript-estree": "4.31.1",
|
"@typescript-eslint/typescript-estree": "4.31.2",
|
||||||
"eslint-scope": "^5.1.1",
|
"eslint-scope": "^5.1.1",
|
||||||
"eslint-utils": "^3.0.0"
|
"eslint-utils": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/parser": {
|
"@typescript-eslint/parser": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.31.2.tgz",
|
||||||
"integrity": "sha512-dnVZDB6FhpIby6yVbHkwTKkn2ypjVIfAR9nh+kYsA/ZL0JlTsd22BiDjouotisY3Irmd3OW1qlk9EI5R8GrvRQ==",
|
"integrity": "sha512-EcdO0E7M/sv23S/rLvenHkb58l3XhuSZzKf6DBvLgHqOYdL6YFMYVtreGFWirxaU2mS1GYDby3Lyxco7X5+Vjw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/scope-manager": "4.31.1",
|
"@typescript-eslint/scope-manager": "4.31.2",
|
||||||
"@typescript-eslint/types": "4.31.1",
|
"@typescript-eslint/types": "4.31.2",
|
||||||
"@typescript-eslint/typescript-estree": "4.31.1",
|
"@typescript-eslint/typescript-estree": "4.31.2",
|
||||||
"debug": "^4.3.1"
|
"debug": "^4.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/scope-manager": {
|
"@typescript-eslint/scope-manager": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.31.2.tgz",
|
||||||
"integrity": "sha512-N1Uhn6SqNtU2XpFSkD4oA+F0PfKdWHyr4bTX0xTj8NRx1314gBDRL1LUuZd5+L3oP+wo6hCbZpaa1in6SwMcVQ==",
|
"integrity": "sha512-2JGwudpFoR/3Czq6mPpE8zBPYdHWFGL6lUNIGolbKQeSNv4EAiHaR5GVDQaLA0FwgcdcMtRk+SBJbFGL7+La5w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.31.1",
|
"@typescript-eslint/types": "4.31.2",
|
||||||
"@typescript-eslint/visitor-keys": "4.31.1"
|
"@typescript-eslint/visitor-keys": "4.31.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/types": {
|
"@typescript-eslint/types": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.31.2.tgz",
|
||||||
"integrity": "sha512-kixltt51ZJGKENNW88IY5MYqTBA8FR0Md8QdGbJD2pKZ+D5IvxjTYDNtJPDxFBiXmka2aJsITdB1BtO1fsgmsQ==",
|
"integrity": "sha512-kWiTTBCTKEdBGrZKwFvOlGNcAsKGJSBc8xLvSjSppFO88AqGxGNYtF36EuEYG6XZ9vT0xX8RNiHbQUKglbSi1w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@typescript-eslint/typescript-estree": {
|
"@typescript-eslint/typescript-estree": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.31.2.tgz",
|
||||||
"integrity": "sha512-EGHkbsUvjFrvRnusk6yFGqrqMBTue5E5ROnS5puj3laGQPasVUgwhrxfcgkdHNFECHAewpvELE1Gjv0XO3mdWg==",
|
"integrity": "sha512-ieBq8U9at6PvaC7/Z6oe8D3czeW5d//Fo1xkF/s9394VR0bg/UaMYPdARiWyKX+lLEjY3w/FNZJxitMsiWv+wA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.31.1",
|
"@typescript-eslint/types": "4.31.2",
|
||||||
"@typescript-eslint/visitor-keys": "4.31.1",
|
"@typescript-eslint/visitor-keys": "4.31.2",
|
||||||
"debug": "^4.3.1",
|
"debug": "^4.3.1",
|
||||||
"globby": "^11.0.3",
|
"globby": "^11.0.3",
|
||||||
"is-glob": "^4.0.1",
|
"is-glob": "^4.0.1",
|
||||||
@ -2984,12 +2984,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@typescript-eslint/visitor-keys": {
|
"@typescript-eslint/visitor-keys": {
|
||||||
"version": "4.31.1",
|
"version": "4.31.2",
|
||||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.1.tgz",
|
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.31.2.tgz",
|
||||||
"integrity": "sha512-PCncP8hEqKw6SOJY+3St4LVtoZpPPn+Zlpm7KW5xnviMhdqcsBty4Lsg4J/VECpJjw1CkROaZhH4B8M1OfnXTQ==",
|
"integrity": "sha512-PrBId7EQq2Nibns7dd/ch6S6/M4/iwLM9McbgeEbCXfxdwRUNxJ4UNreJ6Gh3fI2GNKNrWnQxKL7oCPmngKBug==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@typescript-eslint/types": "4.31.1",
|
"@typescript-eslint/types": "4.31.2",
|
||||||
"eslint-visitor-keys": "^2.0.0"
|
"eslint-visitor-keys": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,4 +1,9 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
import https, { RequestOptions } from 'https';
|
import https, { RequestOptions } from 'https';
|
||||||
|
import { promises as fs } from 'fs';
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import path from 'path';
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,6 +87,28 @@ interface GoogleIDTokenResponse {
|
|||||||
token: string;
|
token: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CreateCredentialsFileParameters are the parameters to generate a Google Cloud
|
||||||
|
* credentials file for use with gcloud and other SDKs.
|
||||||
|
*
|
||||||
|
* @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 requestToken Local environment token to use as authentication to
|
||||||
|
* acquire the real OIDC token.
|
||||||
|
* @param requestURL URL endpoint to use to request the token.
|
||||||
|
* @param outputDir Path to a directory on disk where the file should be
|
||||||
|
* written. The function will determine the file name and write to it securely.
|
||||||
|
*/
|
||||||
|
interface CreateCredentialsFileParameters {
|
||||||
|
providerID: string;
|
||||||
|
serviceAccount: string;
|
||||||
|
requestToken: string;
|
||||||
|
requestURL: string;
|
||||||
|
outputDir: string;
|
||||||
|
}
|
||||||
|
|
||||||
export class Client {
|
export class Client {
|
||||||
/**
|
/**
|
||||||
* request is a high-level helper that returns a promise from the executed
|
* request is a high-level helper that returns a promise from the executed
|
||||||
@ -255,4 +282,47 @@ export class Client {
|
|||||||
throw new Error(`failed to generate Google Cloud ID token for ${serviceAccount}: ${err}`);
|
throw new Error(`failed to generate Google Cloud ID token for ${serviceAccount}: ${err}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* createCredentialsFile creates a Google Cloud credentials file that can be
|
||||||
|
* set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries.
|
||||||
|
*/
|
||||||
|
static async createCredentialsFile({
|
||||||
|
providerID,
|
||||||
|
serviceAccount,
|
||||||
|
requestToken,
|
||||||
|
requestURL,
|
||||||
|
outputDir,
|
||||||
|
}: CreateCredentialsFileParameters): Promise<string> {
|
||||||
|
const data = {
|
||||||
|
type: 'external_account',
|
||||||
|
audience: `//iam.googleapis.com/${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/${serviceAccount}:generateAccessToken`,
|
||||||
|
credential_source: {
|
||||||
|
url: requestURL,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${requestToken}`,
|
||||||
|
},
|
||||||
|
format: {
|
||||||
|
type: 'json',
|
||||||
|
subject_token_field_name: 'value',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// entire leading path).
|
||||||
|
const uniqueName = crypto.randomBytes(12).toString('hex');
|
||||||
|
const pth = path.join(outputDir, uniqueName);
|
||||||
|
|
||||||
|
// Write the file as 0640 so the owner has RW, group as R, and the file is
|
||||||
|
// otherwise unreadable. Also write with EXCL to prevent a symlink attack.
|
||||||
|
await fs.writeFile(pth, JSON.stringify(data), { mode: 0o640, flag: 'wx' });
|
||||||
|
|
||||||
|
return pth;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
87
src/main.ts
87
src/main.ts
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import * as core from '@actions/core';
|
import * as core from '@actions/core';
|
||||||
import { Client } from './client';
|
import { Client } from './client';
|
||||||
|
import { URL } from 'url';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a multi-line or comma-separated collection of strings into an array
|
* Converts a multi-line or comma-separated collection of strings into an array
|
||||||
@ -35,26 +36,79 @@ async function run(): Promise<void> {
|
|||||||
});
|
});
|
||||||
const serviceAccount = core.getInput('service_account', { required: true });
|
const serviceAccount = core.getInput('service_account', { required: true });
|
||||||
const audience = core.getInput('audience');
|
const audience = core.getInput('audience');
|
||||||
const tokenFormat = core.getInput('token_format', { required: true });
|
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 delegates = explodeStrings(core.getInput('delegates'));
|
||||||
const accessTokenLifetime = core.getInput('access_token_lifetime');
|
|
||||||
const accessTokenScopes = explodeStrings(core.getInput('access_token_scopes'));
|
|
||||||
const idTokenAudience = core.getInput('id_token_audience');
|
|
||||||
const idTokenIncludeEmail = core.getBooleanInput('id_token_include_email');
|
|
||||||
|
|
||||||
// Get the GitHub OIDC token.
|
// Always write the credentials file first, before trying to generate
|
||||||
const githubOIDCToken = await core.getIDToken(audience);
|
// tokens. This will ensure the file is written even if token generation
|
||||||
|
// fails, which means continue-on-error actions will still have the file
|
||||||
|
// available.
|
||||||
|
if (createCredentialsFile) {
|
||||||
|
const runnerTempDir = process.env.RUNNER_TEMP!;
|
||||||
|
|
||||||
// Exchange the GitHub OIDC token for a Google Federated Token.
|
// Extract the request token and request URL from the environment. These
|
||||||
const googleFederatedToken = await Client.googleFederatedToken({
|
// are only set when an id-token is requested and the submitter has
|
||||||
providerID: workloadIdentityProvider,
|
// collaborator permissions.
|
||||||
token: githubOIDCToken,
|
const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
|
||||||
});
|
if (!requestToken) {
|
||||||
core.setSecret(googleFederatedToken);
|
throw new Error('$ACTIONS_ID_TOKEN_REQUEST_TOKEN is not set');
|
||||||
|
}
|
||||||
|
const requestURLRaw = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
|
||||||
|
if (!requestURLRaw) {
|
||||||
|
throw new Error('$ACTIONS_ID_TOKEN_REQUEST_URL is not set');
|
||||||
|
}
|
||||||
|
const requestURL = new URL(requestURLRaw);
|
||||||
|
|
||||||
|
// Append the audience value to the request.
|
||||||
|
const params = requestURL.searchParams;
|
||||||
|
params.set('audience', audience);
|
||||||
|
requestURL.search = params.toString();
|
||||||
|
|
||||||
|
// Create the credentials file.
|
||||||
|
const outputPath = await Client.createCredentialsFile({
|
||||||
|
providerID: workloadIdentityProvider,
|
||||||
|
serviceAccount: serviceAccount,
|
||||||
|
requestToken: requestToken,
|
||||||
|
requestURL: requestURL.toString(),
|
||||||
|
outputDir: runnerTempDir,
|
||||||
|
});
|
||||||
|
core.setOutput('credentials_file_path', outputPath);
|
||||||
|
|
||||||
|
// Also set the magic environment variable for gcloud and SDKs if
|
||||||
|
// requested.
|
||||||
|
if (activateCredentialsFile) {
|
||||||
|
core.exportVariable('GOOGLE_APPLICATION_CREDENTIALS', outputPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getFederatedToken is a closure that gets the federated token.
|
||||||
|
const getFederatedToken = async (): Promise<string> => {
|
||||||
|
// Get the GitHub OIDC token.
|
||||||
|
const githubOIDCToken = await core.getIDToken(audience);
|
||||||
|
|
||||||
|
// Exchange the GitHub OIDC token for a Google Federated Token.
|
||||||
|
const googleFederatedToken = await Client.googleFederatedToken({
|
||||||
|
providerID: workloadIdentityProvider,
|
||||||
|
token: githubOIDCToken,
|
||||||
|
});
|
||||||
|
core.setSecret(googleFederatedToken);
|
||||||
|
return googleFederatedToken;
|
||||||
|
};
|
||||||
|
|
||||||
switch (tokenFormat) {
|
switch (tokenFormat) {
|
||||||
|
case '': {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case null: {
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'access_token': {
|
case 'access_token': {
|
||||||
// Exchange the Google Federated Token for an access token.
|
const accessTokenLifetime = core.getInput('access_token_lifetime');
|
||||||
|
const accessTokenScopes = explodeStrings(core.getInput('access_token_scopes'));
|
||||||
|
|
||||||
|
const googleFederatedToken = await getFederatedToken();
|
||||||
const { accessToken, expiration } = await Client.googleAccessToken({
|
const { accessToken, expiration } = await Client.googleAccessToken({
|
||||||
token: googleFederatedToken,
|
token: googleFederatedToken,
|
||||||
serviceAccount: serviceAccount,
|
serviceAccount: serviceAccount,
|
||||||
@ -68,7 +122,10 @@ async function run(): Promise<void> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'id_token': {
|
case 'id_token': {
|
||||||
// Exchange the Google Federated Token for an id token.
|
const idTokenAudience = core.getInput('id_token_audience', { required: true });
|
||||||
|
const idTokenIncludeEmail = core.getBooleanInput('id_token_include_email');
|
||||||
|
|
||||||
|
const googleFederatedToken = await getFederatedToken();
|
||||||
const { token } = await Client.googleIDToken({
|
const { token } = await Client.googleIDToken({
|
||||||
token: googleFederatedToken,
|
token: googleFederatedToken,
|
||||||
serviceAccount: serviceAccount,
|
serviceAccount: serviceAccount,
|
||||||
|
Loading…
Reference in New Issue
Block a user