Update documentation and emit log messages (#85)

This commit is contained in:
Seth Vargo 2021-12-09 17:27:21 -05:00 committed by GitHub
parent d03480e8ad
commit ac09c292a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 6 deletions

View File

@ -174,6 +174,21 @@ regardless of the authentication mechanism.
generate a credentials file which can be used for authentication via gcloud generate a credentials file which can be used for authentication via gcloud
and Google Cloud SDKs in other steps in the workflow. The default is true. and Google Cloud SDKs in other steps in the workflow. The default is true.
The credentials file is exported into `$GITHUB_WORKSPACE`, which makes it
available to all future steps and filesystems (including Docker-based
GitHub Actions). The file is automatically removed at the end of the job
via a post action. In order to use exported credentials, you **must** add
the `actions/checkout` step before calling `auth`. This is due to how
GitHub Actions creates `$GITHUB_WORKSPACE`:
```yaml
jobs:
job_id:
steps:
- uses: 'actions/checkout@v2' # Must come first!
- uses: 'google-github-actions/auth@v0'
```
- `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
delegates. delegates.
@ -419,7 +434,7 @@ the [gcloud][gcloud] command-line tool.
--display-name="Demo pool" --display-name="Demo pool"
``` ```
1. Get the full ID of the Workload Identity Pool: 1. Get the full ID of the Workload Identity **Pool**:
```sh ```sh
gcloud iam workload-identity-pools describe "my-pool" \ gcloud iam workload-identity-pools describe "my-pool" \
@ -435,7 +450,7 @@ the [gcloud][gcloud] command-line tool.
``` ```
1. Create a Workload Identity Provider in that pool: 1. Create a Workload Identity **Provider** in that pool:
```sh ```sh
gcloud iam workload-identity-pools providers create-oidc "my-provider" \ gcloud iam workload-identity-pools providers create-oidc "my-provider" \
@ -470,10 +485,24 @@ the [gcloud][gcloud] command-line tool.
export REPO="username/name" # e.g. "google/chrome" export REPO="username/name" # e.g. "google/chrome"
gcloud iam service-accounts add-iam-policy-binding "my-service-account@${PROJECT_ID}.iam.gserviceaccount.com" \ gcloud iam service-accounts add-iam-policy-binding "my-service-account@${PROJECT_ID}.iam.gserviceaccount.com" \
--project="${PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \ --role="roles/iam.workloadIdentityUser" \
--member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${REPO}" --member="principalSet://iam.googleapis.com/${WORKLOAD_IDENTITY_POOL_ID}/attribute.repository/${REPO}"
``` ```
1. Extract the Workload Identity **Provider** resource name:
```sh
gcloud iam workload-identity-pools providers describe "my-provider" \
--project="${PROJECT_ID}"
--location="global" \
--workload-identity-pool="my-pool" \
--format='value(name)'
```
Use this value as the `workload_identity_provider` value in your GitHub
Actions YAML.
1. Use this GitHub Action with the Workload Identity Provider ID and Service 1. Use this GitHub Action with the Workload Identity Provider ID and Service
Account email. The GitHub Action will mint a GitHub OIDC token and exchange Account email. The GitHub Action will mint a GitHub OIDC token and exchange
the GitHub token for a Google Cloud access token (assuming the authorization the GitHub token for a Google Cloud access token (assuming the authorization

2
dist/main/index.js vendored

File diff suppressed because one or more lines are too long

2
dist/post/index.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,12 @@
'use strict'; 'use strict';
import { import {
debug as logDebug,
exportVariable, exportVariable,
getBooleanInput, getBooleanInput,
getIDToken, getIDToken,
getInput, getInput,
info as logInfo,
setFailed, setFailed,
setOutput, setOutput,
setSecret, setSecret,
@ -13,7 +15,7 @@ import { WorkloadIdentityClient } from './client/workload_identity_client';
import { CredentialsJSONClient } from './client/credentials_json_client'; import { CredentialsJSONClient } from './client/credentials_json_client';
import { AuthClient } from './client/auth_client'; import { AuthClient } from './client/auth_client';
import { BaseClient } from './base'; import { BaseClient } from './base';
import { buildDomainWideDelegationJWT, explodeStrings, parseDuration } from './utils'; import { buildDomainWideDelegationJWT, errorMessage, explodeStrings, parseDuration } from './utils';
const secretsWarning = const secretsWarning =
`If you are specifying input values via GitHub secrets, ensure the secret ` + `If you are specifying input values via GitHub secrets, ensure the secret ` +
@ -67,6 +69,8 @@ async function run(): Promise<void> {
// Instantiate the correct client based on the provided input parameters. // Instantiate the correct client based on the provided input parameters.
let client: AuthClient; let client: AuthClient;
if (workloadIdentityProvider) { if (workloadIdentityProvider) {
logDebug(`Using workload identity provider "${workloadIdentityProvider}"`);
// If we're going to do the OIDC dance, we need to make sure these values // If we're going to do the OIDC dance, we need to make sure these values
// are set. If they aren't, core.getIDToken() will fail and so will // are set. If they aren't, core.getIDToken() will fail and so will
// generating the credentials file. // generating the credentials file.
@ -87,6 +91,7 @@ async function run(): Promise<void> {
oidcTokenRequestURL: oidcTokenRequestURL, oidcTokenRequestURL: oidcTokenRequestURL,
}); });
} else { } else {
logDebug(`Using credentials JSON`);
client = new CredentialsJSONClient({ client = new CredentialsJSONClient({
projectID: projectID, projectID: projectID,
credentialsJSON: credentialsJSON, credentialsJSON: credentialsJSON,
@ -98,6 +103,8 @@ async function run(): Promise<void> {
// fails, which means continue-on-error actions will still have the file // fails, which means continue-on-error actions will still have the file
// available. // available.
if (createCredentialsFile) { if (createCredentialsFile) {
logDebug(`Creating credentials file`);
// Note: We explicitly and intentionally export to GITHUB_WORKSPACE // Note: We explicitly and intentionally export to GITHUB_WORKSPACE
// instead of RUNNER_TEMP, because RUNNER_TEMP is not shared with // instead of RUNNER_TEMP, because RUNNER_TEMP is not shared with
// Docker-based actions on the filesystem. Exporting to GITHUB_WORKSPACE // Docker-based actions on the filesystem. Exporting to GITHUB_WORKSPACE
@ -113,14 +120,21 @@ async function run(): Promise<void> {
throw new Error('$GITHUB_WORKSPACE is not set'); throw new Error('$GITHUB_WORKSPACE is not set');
} }
// Create credentials file.
const credentialsPath = await client.createCredentialsFile(githubWorkspace); const credentialsPath = await client.createCredentialsFile(githubWorkspace);
logInfo(`Created credentials file at "${credentialsPath}"`);
// Output to be available to future steps.
setOutput('credentials_file_path', credentialsPath); setOutput('credentials_file_path', credentialsPath);
// CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE is picked up by gcloud to use // CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE is picked up by gcloud to use
// a specific credential file (subject to change and equivalent to auth/credential_file_override) // a specific credential file (subject to change and equivalent to auth/credential_file_override)
exportVariable('CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE', credentialsPath); exportVariable('CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE', credentialsPath);
// GOOGLE_APPLICATION_CREDENTIALS is used by Application Default Credentials // GOOGLE_APPLICATION_CREDENTIALS is used by Application Default Credentials
// in all GCP client libraries // in all GCP client libraries
exportVariable('GOOGLE_APPLICATION_CREDENTIALS', credentialsPath); exportVariable('GOOGLE_APPLICATION_CREDENTIALS', credentialsPath);
// GOOGLE_GHA_CREDS_PATH is used by other Google GitHub Actions // GOOGLE_GHA_CREDS_PATH is used by other Google GitHub Actions
exportVariable('GOOGLE_GHA_CREDS_PATH', credentialsPath); exportVariable('GOOGLE_GHA_CREDS_PATH', credentialsPath);
} }
@ -142,6 +156,8 @@ async function run(): Promise<void> {
break; break;
} }
case 'access_token': { case 'access_token': {
logDebug(`Creating access token`);
const accessTokenLifetime = parseDuration(getInput('access_token_lifetime')); const accessTokenLifetime = parseDuration(getInput('access_token_lifetime'));
const accessTokenScopes = explodeStrings(getInput('access_token_scopes')); const accessTokenScopes = explodeStrings(getInput('access_token_scopes'));
const accessTokenSubject = getInput('access_token_subject'); const accessTokenSubject = getInput('access_token_subject');
@ -152,6 +168,12 @@ async function run(): Promise<void> {
// Credentials endpoints. // Credentials endpoints.
let accessToken, expiration; let accessToken, expiration;
if (accessTokenSubject) { if (accessTokenSubject) {
logInfo(
`An access token subject was specified, triggering Domain-Wide ` +
`Delegation flow. This flow does not support specifying an ` +
`access token lifetime of greater than 1 hour.`,
);
const unsignedJWT = buildDomainWideDelegationJWT( const unsignedJWT = buildDomainWideDelegationJWT(
serviceAccount, serviceAccount,
accessTokenSubject, accessTokenSubject,
@ -176,6 +198,8 @@ async function run(): Promise<void> {
break; break;
} }
case 'id_token': { case 'id_token': {
logDebug(`Creating id token`);
const idTokenAudience = getInput('id_token_audience', { required: true }); const idTokenAudience = getInput('id_token_audience', { required: true });
const idTokenIncludeEmail = getBooleanInput('id_token_include_email'); const idTokenIncludeEmail = getBooleanInput('id_token_include_email');
const serviceAccount = await client.getServiceAccount(); const serviceAccount = await client.getServiceAccount();
@ -196,7 +220,8 @@ async function run(): Promise<void> {
} }
} }
} catch (err) { } catch (err) {
setFailed(`google-github-actions/auth failed with: ${err}`); const msg = errorMessage(err);
setFailed(`google-github-actions/auth failed with: ${msg}`);
} }
} }

View File

@ -229,3 +229,27 @@ export function buildDomainWideDelegationJWT(
return JSON.stringify(body); return JSON.stringify(body);
} }
/**
* errorMessage extracts the error message from the given error.
*
* TODO(sethvargo): Candidate for centralization.
*
*/
export function errorMessage(err: unknown): string {
if (!err) {
return '';
}
let msg = err instanceof Error ? err.message : `${err}`;
msg = msg.trim();
msg = msg.replace('Error: ', '');
msg = msg.trim();
if (!msg) {
return '';
}
msg = msg[0].toLowerCase() + msg.slice(1);
return msg;
}