Update documentation and emit log messages (#85)
This commit is contained in:
parent
d03480e8ad
commit
ac09c292a1
33
README.md
33
README.md
@ -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
2
dist/main/index.js
vendored
File diff suppressed because one or more lines are too long
2
dist/post/index.js
vendored
2
dist/post/index.js
vendored
File diff suppressed because one or more lines are too long
29
src/main.ts
29
src/main.ts
@ -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}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
24
src/utils.ts
24
src/utils.ts
@ -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;
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user