Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 31 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,21 @@ regardless of the authentication mechanism.
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.

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
identities to use for impersonation in the chain. By default there are no
delegates.
Expand Down Expand Up @@ -419,7 +434,7 @@ the [gcloud][gcloud] command-line tool.
--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
gcloud iam workload-identity-pools describe "my-pool" \
Expand All @@ -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
gcloud iam workload-identity-pools providers create-oidc "my-provider" \
Expand Down Expand Up @@ -470,10 +485,24 @@ the [gcloud][gcloud] command-line tool.
export REPO="username/name" # e.g. "google/chrome"

gcloud iam service-accounts add-iam-policy-binding "my-service-account@${PROJECT_ID}.iam.gserviceaccount.com" \
--project="${PROJECT_ID}" \
--role="roles/iam.workloadIdentityUser" \
--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
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
Expand Down
2 changes: 1 addition & 1 deletion dist/main/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/post/index.js

Large diffs are not rendered by default.

29 changes: 27 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
'use strict';

import {
debug as logDebug,
exportVariable,
getBooleanInput,
getIDToken,
getInput,
info as logInfo,
setFailed,
setOutput,
setSecret,
Expand All @@ -13,7 +15,7 @@ import { WorkloadIdentityClient } from './client/workload_identity_client';
import { CredentialsJSONClient } from './client/credentials_json_client';
import { AuthClient } from './client/auth_client';
import { BaseClient } from './base';
import { buildDomainWideDelegationJWT, explodeStrings, parseDuration } from './utils';
import { buildDomainWideDelegationJWT, errorMessage, explodeStrings, parseDuration } from './utils';

const secretsWarning =
`If you are specifying input values via GitHub secrets, ensure the secret ` +
Expand Down Expand Up @@ -67,6 +69,8 @@ async function run(): Promise<void> {
// Instantiate the correct client based on the provided input parameters.
let client: AuthClient;
if (workloadIdentityProvider) {
logDebug(`Using workload identity provider "${workloadIdentityProvider}"`);

// 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
// generating the credentials file.
Expand All @@ -87,6 +91,7 @@ async function run(): Promise<void> {
oidcTokenRequestURL: oidcTokenRequestURL,
});
} else {
logDebug(`Using credentials JSON`);
client = new CredentialsJSONClient({
projectID: projectID,
credentialsJSON: credentialsJSON,
Expand All @@ -98,6 +103,8 @@ async function run(): Promise<void> {
// fails, which means continue-on-error actions will still have the file
// available.
if (createCredentialsFile) {
logDebug(`Creating credentials file`);

// Note: We explicitly and intentionally export to GITHUB_WORKSPACE
// instead of RUNNER_TEMP, because RUNNER_TEMP is not shared with
// Docker-based actions on the filesystem. Exporting to GITHUB_WORKSPACE
Expand All @@ -113,14 +120,21 @@ async function run(): Promise<void> {
throw new Error('$GITHUB_WORKSPACE is not set');
}

// Create credentials file.
const credentialsPath = await client.createCredentialsFile(githubWorkspace);
logInfo(`Created credentials file at "${credentialsPath}"`);

// Output to be available to future steps.
setOutput('credentials_file_path', credentialsPath);

// 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)
exportVariable('CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE', credentialsPath);

// GOOGLE_APPLICATION_CREDENTIALS is used by Application Default Credentials
// in all GCP client libraries
exportVariable('GOOGLE_APPLICATION_CREDENTIALS', credentialsPath);

// GOOGLE_GHA_CREDS_PATH is used by other Google GitHub Actions
exportVariable('GOOGLE_GHA_CREDS_PATH', credentialsPath);
}
Expand All @@ -142,6 +156,8 @@ async function run(): Promise<void> {
break;
}
case 'access_token': {
logDebug(`Creating access token`);

const accessTokenLifetime = parseDuration(getInput('access_token_lifetime'));
const accessTokenScopes = explodeStrings(getInput('access_token_scopes'));
const accessTokenSubject = getInput('access_token_subject');
Expand All @@ -152,6 +168,12 @@ async function run(): Promise<void> {
// Credentials endpoints.
let accessToken, expiration;
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(
serviceAccount,
accessTokenSubject,
Expand All @@ -176,6 +198,8 @@ async function run(): Promise<void> {
break;
}
case 'id_token': {
logDebug(`Creating id token`);

const idTokenAudience = getInput('id_token_audience', { required: true });
const idTokenIncludeEmail = getBooleanInput('id_token_include_email');
const serviceAccount = await client.getServiceAccount();
Expand All @@ -196,7 +220,8 @@ async function run(): Promise<void> {
}
}
} catch (err) {
setFailed(`google-github-actions/auth failed with: ${err}`);
const msg = errorMessage(err);
setFailed(`google-github-actions/auth failed with: ${msg}`);
}
}

Expand Down
24 changes: 24 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,27 @@ export function buildDomainWideDelegationJWT(

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