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:
Seth Vargo 2021-09-21 18:10:27 -04:00 committed by GitHub
parent c7bb6ad28f
commit febe21311b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 571 additions and 153 deletions

View File

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

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

View File

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

115
dist/index.js vendored
View File

@ -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,12 +226,49 @@ 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.
if (createCredentialsFile) {
const runnerTempDir = process.env.RUNNER_TEMP;
// 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;
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. // Get the GitHub OIDC token.
const githubOIDCToken = yield core.getIDToken(audience); const githubOIDCToken = yield core.getIDToken(audience);
// Exchange the GitHub OIDC token for a Google Federated Token. // Exchange the GitHub OIDC token for a Google Federated Token.
@ -239,9 +277,19 @@ function run() {
token: githubOIDCToken, token: githubOIDCToken,
}); });
core.setSecret(googleFederatedToken); 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 = 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
View File

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

View File

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

View File

@ -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,13 +36,55 @@ 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');
// 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
// available.
if (createCredentialsFile) {
const runnerTempDir = process.env.RUNNER_TEMP!;
// 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;
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(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. // Get the GitHub OIDC token.
const githubOIDCToken = await core.getIDToken(audience); const githubOIDCToken = await core.getIDToken(audience);
@ -51,10 +94,21 @@ async function run(): Promise<void> {
token: githubOIDCToken, token: githubOIDCToken,
}); });
core.setSecret(googleFederatedToken); 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,