Emit a better error when OIDC information is missing (#81)

This commit is contained in:
Seth Vargo 2021-12-09 12:52:57 -05:00 committed by GitHub
parent 1618f1c032
commit ccc7806970
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 44 additions and 25 deletions

2
dist/main/index.js vendored

File diff suppressed because one or more lines are too long

View File

@ -25,6 +25,9 @@ interface WorkloadIdentityClientOptions {
serviceAccount: string; serviceAccount: string;
token: string; token: string;
audience: string; audience: string;
oidcTokenRequestURL: string;
oidcTokenRequestToken: string;
} }
/** /**
@ -38,12 +41,18 @@ export class WorkloadIdentityClient implements AuthClient {
readonly #token: string; readonly #token: string;
readonly #audience: string; readonly #audience: string;
readonly #oidcTokenRequestURL: string;
readonly #oidcTokenRequestToken: string;
constructor(opts: WorkloadIdentityClientOptions) { constructor(opts: WorkloadIdentityClientOptions) {
this.#providerID = opts.providerID; this.#providerID = opts.providerID;
this.#serviceAccount = opts.serviceAccount; this.#serviceAccount = opts.serviceAccount;
this.#token = opts.token; this.#token = opts.token;
this.#audience = opts.audience; this.#audience = opts.audience;
this.#oidcTokenRequestURL = opts.oidcTokenRequestURL;
this.#oidcTokenRequestToken = opts.oidcTokenRequestToken;
this.#projectID = this.#projectID =
opts.projectID || this.extractProjectIDFromServiceAccountEmail(this.#serviceAccount); opts.projectID || this.extractProjectIDFromServiceAccountEmail(this.#serviceAccount);
} }
@ -173,21 +182,7 @@ export class WorkloadIdentityClient implements AuthClient {
* set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries. * set as GOOGLE_APPLICATION_CREDENTIALS for gcloud and client libraries.
*/ */
async createCredentialsFile(outputDir: string): Promise<string> { async createCredentialsFile(outputDir: string): Promise<string> {
// Extract the request token and request URL from the environment. These const requestURL = new URL(this.#oidcTokenRequestURL);
// are only set when an id-token is requested and the submitter has
// collaborator permissions.
const requestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
const requestURLRaw = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
if (!requestToken || !requestURLRaw) {
throw new Error(
'GitHub Actions did not inject $ACTIONS_ID_TOKEN_REQUEST_TOKEN or ' +
'$ACTIONS_ID_TOKEN_REQUEST_URL into this job. This most likely ' +
'means the GitHub Actions workflow permissions are incorrect, or ' +
'this job is being run from a fork. For more information, please ' +
'see the GitHub documentation at https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token',
);
}
const requestURL = new URL(requestURLRaw);
// Append the audience value to the request. // Append the audience value to the request.
const params = requestURL.searchParams; const params = requestURL.searchParams;
@ -204,7 +199,7 @@ export class WorkloadIdentityClient implements AuthClient {
credential_source: { credential_source: {
url: requestURL, url: requestURL,
headers: { headers: {
Authorization: `Bearer ${requestToken}`, Authorization: `Bearer ${this.#oidcTokenRequestToken}`,
}, },
format: { format: {
type: 'json', type: 'json',

View File

@ -16,9 +16,15 @@ import { BaseClient } from './base';
import { buildDomainWideDelegationJWT, explodeStrings, parseDuration } from './utils'; import { buildDomainWideDelegationJWT, 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 ` +
'is being injected into the environment. By default, secrets are not passed ' + `is being injected into the environment. By default, secrets are not ` +
'to workflows triggered from forks, including Dependabot.'; `passed to workflows triggered from forks, including Dependabot.`;
const oidcWarning =
`GitHub Actions did not inject $ACTIONS_ID_TOKEN_REQUEST_TOKEN or ` +
`$ACTIONS_ID_TOKEN_REQUEST_URL into this job. This most likely means the ` +
`GitHub Actions workflow permissions are incorrect, or this job is being ` +
`run from a fork. For more information, please see https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token`;
/** /**
* Executes the main action, documented inline. * Executes the main action, documented inline.
@ -61,6 +67,15 @@ 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) {
// 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.
const oidcTokenRequestToken = process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
const oidcTokenRequestURL = process.env.ACTIONS_ID_TOKEN_REQUEST_URL;
if (!oidcTokenRequestToken || !oidcTokenRequestURL) {
throw new Error(oidcWarning);
}
const token = await getIDToken(audience); const token = await getIDToken(audience);
client = new WorkloadIdentityClient({ client = new WorkloadIdentityClient({
projectID: projectID, projectID: projectID,
@ -68,6 +83,8 @@ async function run(): Promise<void> {
serviceAccount: serviceAccount, serviceAccount: serviceAccount,
token: token, token: token,
audience: audience, audience: audience,
oidcTokenRequestToken: oidcTokenRequestToken,
oidcTokenRequestURL: oidcTokenRequestURL,
}); });
} else { } else {
client = new CredentialsJSONClient({ client = new CredentialsJSONClient({

View File

@ -15,6 +15,8 @@ describe('WorkloadIdentityClient', () => {
token: 'my-token', token: 'my-token',
serviceAccount: 'my-service@my-project.iam.gserviceaccount.com', serviceAccount: 'my-service@my-project.iam.gserviceaccount.com',
audience: 'my-aud', audience: 'my-aud',
oidcTokenRequestURL: 'https://example.com/',
oidcTokenRequestToken: 'token',
}); });
const result = await client.getProjectID(); const result = await client.getProjectID();
@ -28,6 +30,8 @@ describe('WorkloadIdentityClient', () => {
token: 'my-token', token: 'my-token',
serviceAccount: 'my-service@my-project.iam.gserviceaccount.com', serviceAccount: 'my-service@my-project.iam.gserviceaccount.com',
audience: 'my-aud', audience: 'my-aud',
oidcTokenRequestURL: 'https://example.com/',
oidcTokenRequestToken: 'token',
}); });
const result = await client.getProjectID(); const result = await client.getProjectID();
@ -41,6 +45,8 @@ describe('WorkloadIdentityClient', () => {
token: 'my-token', token: 'my-token',
serviceAccount: 'my-service@developers.google.com', serviceAccount: 'my-service@developers.google.com',
audience: 'my-aud', audience: 'my-aud',
oidcTokenRequestURL: 'https://example.com/',
oidcTokenRequestToken: 'token',
}); });
}; };
return expect(fn).to.throw(Error); return expect(fn).to.throw(Error);
@ -55,6 +61,8 @@ describe('WorkloadIdentityClient', () => {
serviceAccount: 'my-service@my-project.iam.gserviceaccount.com', serviceAccount: 'my-service@my-project.iam.gserviceaccount.com',
token: 'my-token', token: 'my-token',
audience: 'my-aud', audience: 'my-aud',
oidcTokenRequestURL: 'https://example.com/',
oidcTokenRequestToken: 'token',
}); });
const result = await client.getServiceAccount(); const result = await client.getServiceAccount();
expect(result).to.eq('my-service@my-project.iam.gserviceaccount.com'); expect(result).to.eq('my-service@my-project.iam.gserviceaccount.com');
@ -63,9 +71,6 @@ describe('WorkloadIdentityClient', () => {
describe('#createCredentialsFile', () => { describe('#createCredentialsFile', () => {
it('writes the file', async () => { it('writes the file', async () => {
process.env.ACTIONS_ID_TOKEN_REQUEST_URL = 'https://actions-token.url';
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'github-token';
const tmp = tmpdir(); const tmp = tmpdir();
const client = new WorkloadIdentityClient({ const client = new WorkloadIdentityClient({
projectID: 'my-project', projectID: 'my-project',
@ -73,6 +78,8 @@ describe('WorkloadIdentityClient', () => {
serviceAccount: 'my-service@my-project.iam.gserviceaccount.com', serviceAccount: 'my-service@my-project.iam.gserviceaccount.com',
token: 'my-token', token: 'my-token',
audience: 'my-aud', audience: 'my-aud',
oidcTokenRequestURL: 'https://example.com/',
oidcTokenRequestToken: 'token',
}); });
const exp = { const exp = {
@ -83,9 +90,9 @@ describe('WorkloadIdentityClient', () => {
type: 'json', type: 'json',
}, },
headers: { headers: {
Authorization: 'Bearer github-token', Authorization: 'Bearer token',
}, },
url: 'https://actions-token.url/?audience=my-aud', url: 'https://example.com/?audience=my-aud',
}, },
service_account_impersonation_url: service_account_impersonation_url:
'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/my-service@my-project.iam.gserviceaccount.com:generateAccessToken', 'https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/my-service@my-project.iam.gserviceaccount.com:generateAccessToken',