fix webIdentityTokenFile option, rearrange validation logic

This commit is contained in:
peterwoodworth
2023-06-30 15:40:41 -07:00
parent 7526948ef9
commit 2b32a8f2c6
4 changed files with 53 additions and 80 deletions

2
dist/cleanup/src/helpers.d.ts generated vendored
View File

@@ -2,7 +2,7 @@ import type { Credentials } from '@aws-sdk/client-sts';
import type { CredentialsClient } from './CredentialsClient'; import type { CredentialsClient } from './CredentialsClient';
export declare function exportCredentials(creds?: Partial<Credentials>): void; export declare function exportCredentials(creds?: Partial<Credentials>): void;
export declare function exportRegion(region: string): void; export declare function exportRegion(region: string): void;
export declare function exportAccountId(credentialsClient: CredentialsClient, maskAccountId?: string): Promise<string>; export declare function exportAccountId(credentialsClient: CredentialsClient, maskAccountId?: boolean): Promise<string>;
export declare function sanitizeGitHubVariables(name: string): string; export declare function sanitizeGitHubVariables(name: string): string;
export declare function defaultSleep(ms: number): Promise<unknown>; export declare function defaultSleep(ms: number): Promise<unknown>;
declare let sleep: typeof defaultSleep; declare let sleep: typeof defaultSleep;

58
dist/index.js generated vendored
View File

@@ -149,7 +149,7 @@ async function assumeRoleWithCredentials(params, client) {
} }
} }
async function assumeRole(params) { async function assumeRole(params) {
const { credentialsClient, sourceAccountId, roleToAssume, roleExternalId, roleDuration, roleSessionName, roleSkipSessionTagging, webIdentityTokenFile, webIdentityToken, inlineSessionPolicy, managedSessionPolicies } = { ...params }; const { credentialsClient, sourceAccountId, roleToAssume, roleExternalId, roleDuration, roleSessionName, roleSkipSessionTagging, webIdentityTokenFile, webIdentityToken, inlineSessionPolicy, managedSessionPolicies, } = { ...params };
// Load GitHub environment variables // Load GitHub environment variables
const { GITHUB_REPOSITORY, GITHUB_WORKFLOW, GITHUB_ACTION, GITHUB_ACTOR, GITHUB_SHA, GITHUB_WORKSPACE } = process.env; const { GITHUB_REPOSITORY, GITHUB_WORKFLOW, GITHUB_ACTION, GITHUB_ACTOR, GITHUB_SHA, GITHUB_WORKSPACE } = process.env;
if (!GITHUB_REPOSITORY || !GITHUB_WORKFLOW || !GITHUB_ACTION || !GITHUB_ACTOR || !GITHUB_SHA || !GITHUB_WORKSPACE) { if (!GITHUB_REPOSITORY || !GITHUB_WORKFLOW || !GITHUB_ACTION || !GITHUB_ACTOR || !GITHUB_SHA || !GITHUB_WORKSPACE) {
@@ -188,7 +188,7 @@ async function assumeRole(params) {
Tags: tags ? tags : undefined, Tags: tags ? tags : undefined,
ExternalId: roleExternalId ? roleExternalId : undefined, ExternalId: roleExternalId ? roleExternalId : undefined,
Policy: inlineSessionPolicy ? inlineSessionPolicy : undefined, Policy: inlineSessionPolicy ? inlineSessionPolicy : undefined,
PolicyArns: managedSessionPolicies ? managedSessionPolicies : undefined, PolicyArns: managedSessionPolicies?.length ? managedSessionPolicies : undefined,
}; };
const keys = Object.keys(commonAssumeRoleParams); const keys = Object.keys(commonAssumeRoleParams);
keys.forEach((k) => commonAssumeRoleParams[k] === undefined && delete commonAssumeRoleParams[k]); keys.forEach((k) => commonAssumeRoleParams[k] === undefined && delete commonAssumeRoleParams[k]);
@@ -389,7 +389,7 @@ async function run() {
const region = core.getInput('aws-region', { required: true }); const region = core.getInput('aws-region', { required: true });
const roleToAssume = core.getInput('role-to-assume', { required: false }); const roleToAssume = core.getInput('role-to-assume', { required: false });
const audience = core.getInput('audience', { required: false }); const audience = core.getInput('audience', { required: false });
const maskAccountId = core.getInput('mask-aws-account-id', { required: false }); const maskAccountId = core.getBooleanInput('mask-aws-account-id', { required: false });
const roleExternalId = core.getInput('role-external-id', { required: false }); const roleExternalId = core.getInput('role-external-id', { required: false });
const webIdentityTokenFile = core.getInput('web-identity-token-file', { required: false }); const webIdentityTokenFile = core.getInput('web-identity-token-file', { required: false });
const roleDuration = parseInt(core.getInput('role-duration-seconds', { required: false })) || DEFAULT_ROLE_DURATION; const roleDuration = parseInt(core.getInput('role-duration-seconds', { required: false })) || DEFAULT_ROLE_DURATION;
@@ -401,11 +401,11 @@ async function run() {
const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false }); const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false });
const managedSessionPoliciesInput = core.getMultilineInput('managed-session-policies', { required: false }); const managedSessionPoliciesInput = core.getMultilineInput('managed-session-policies', { required: false });
const managedSessionPolicies = []; const managedSessionPolicies = [];
const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false';
const roleChaining = roleChainingInput.toLowerCase() === 'true';
for (const managedSessionPolicy of managedSessionPoliciesInput) { for (const managedSessionPolicy of managedSessionPoliciesInput) {
managedSessionPolicies.push({ arn: managedSessionPolicy }); managedSessionPolicies.push({ arn: managedSessionPolicy });
} }
const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false';
const roleChaining = roleChainingInput.toLowerCase() === 'true';
// Logic to decide whether to attempt to use OIDC or not // Logic to decide whether to attempt to use OIDC or not
const useGitHubOIDCProvider = () => { const useGitHubOIDCProvider = () => {
// The `ACTIONS_ID_TOKEN_REQUEST_TOKEN` environment variable is set when the `id-token` permission is granted. // The `ACTIONS_ID_TOKEN_REQUEST_TOKEN` environment variable is set when the `id-token` permission is granted.
@@ -427,40 +427,36 @@ async function run() {
!disableOIDC && !disableOIDC &&
!roleChaining); !roleChaining);
}; };
// Validate and export region if (!region.match(REGION_REGEX)) {
if (region) { throw new Error(`Region is not valid: ${region}`);
if (!region.match(REGION_REGEX)) {
throw new Error(`Region is not valid: ${region}`);
}
(0, helpers_1.exportRegion)(region);
} }
(0, helpers_1.exportRegion)(region);
// Instantiate credentials client // Instantiate credentials client
const credentialsClient = new CredentialsClient_1.CredentialsClient({ region, proxyServer }); const credentialsClient = new CredentialsClient_1.CredentialsClient({ region, proxyServer });
// Always export the source credentials and account ID. let sourceAccountId;
// The STS client for calling AssumeRole pulls creds from the environment. let webIdentityToken;
// Plus, in the assume role case, if the AssumeRole call fails, we want // If OIDC is being used, generate token
// the source credentials and account ID to already be masked as secrets // Else, validate that the SDK can pick up credentials
// in any error messages. if (useGitHubOIDCProvider()) {
if (AccessKeyId) { webIdentityToken = await core.getIDToken(audience);
}
else if (AccessKeyId) {
if (!SecretAccessKey) { if (!SecretAccessKey) {
throw new Error("'aws-secret-access-key' must be provided if 'aws-access-key-id' is provided"); throw new Error("'aws-secret-access-key' must be provided if 'aws-access-key-id' is provided");
} }
// The STS client for calling AssumeRole pulls creds from the environment.
// Plus, in the assume role case, if the AssumeRole call fails, we want
// the source credentials and account ID to already be masked as secrets
// in any error messages.
(0, helpers_1.exportCredentials)({ AccessKeyId, SecretAccessKey, SessionToken }); (0, helpers_1.exportCredentials)({ AccessKeyId, SecretAccessKey, SessionToken });
} }
// If OIDC is being used, generate token else if (!webIdentityTokenFile && !roleChaining) {
// Else, validate that the SDK can pick up credentials throw new Error('Could not determine how to assume credentials. Please check your inputs and try again.');
let sourceAccountId;
let webIdentityToken;
if (useGitHubOIDCProvider()) {
webIdentityToken = await core.getIDToken(audience);
// Implement #359
} }
else { if (AccessKeyId || roleChaining) {
// Regardless of whether any source credentials were provided as inputs, // Validate that the SDK can actually pick up credentials.
// validate that the SDK can actually pick up credentials. This validates // This validates cases where this action is using existing environment credentials,
// cases where this action is on a self-hosted runner that doesn't have credentials // and cases where the user intended to provide input credentials but the secrets inputs resolved to empty strings.
// configured correctly, and cases where the user intended to provide input
// credentials but the secrets inputs resolved to empty strings.
await credentialsClient.validateCredentials(AccessKeyId, roleChaining); await credentialsClient.validateCredentials(AccessKeyId, roleChaining);
sourceAccountId = await (0, helpers_1.exportAccountId)(credentialsClient, maskAccountId); sourceAccountId = await (0, helpers_1.exportAccountId)(credentialsClient, maskAccountId);
} }
@@ -491,10 +487,8 @@ async function run() {
await credentialsClient.validateCredentials(roleCredentials.Credentials?.AccessKeyId); await credentialsClient.validateCredentials(roleCredentials.Credentials?.AccessKeyId);
} }
await (0, helpers_1.exportAccountId)(credentialsClient, maskAccountId); await (0, helpers_1.exportAccountId)(credentialsClient, maskAccountId);
// implement #432
} }
else { else {
// implement #370
core.info('Proceeding with IAM user credentials'); core.info('Proceeding with IAM user credentials');
} }
} }

View File

@@ -29,11 +29,11 @@ export async function run() {
const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false }); const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false });
const managedSessionPoliciesInput = core.getMultilineInput('managed-session-policies', { required: false }); const managedSessionPoliciesInput = core.getMultilineInput('managed-session-policies', { required: false });
const managedSessionPolicies: any[] = []; const managedSessionPolicies: any[] = [];
const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false';
const roleChaining = roleChainingInput.toLowerCase() === 'true';
for (const managedSessionPolicy of managedSessionPoliciesInput) { for (const managedSessionPolicy of managedSessionPoliciesInput) {
managedSessionPolicies.push({ arn: managedSessionPolicy }); managedSessionPolicies.push({ arn: managedSessionPolicy });
} }
const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false';
const roleChaining = roleChainingInput.toLowerCase() === 'true';
// Logic to decide whether to attempt to use OIDC or not // Logic to decide whether to attempt to use OIDC or not
const useGitHubOIDCProvider = () => { const useGitHubOIDCProvider = () => {
@@ -63,44 +63,38 @@ export async function run() {
); );
}; };
// Validate and export region if (!region.match(REGION_REGEX)) {
if (region) { throw new Error(`Region is not valid: ${region}`);
if (!region.match(REGION_REGEX)) {
throw new Error(`Region is not valid: ${region}`);
}
exportRegion(region);
} }
exportRegion(region);
// Instantiate credentials client // Instantiate credentials client
const credentialsClient = new CredentialsClient({ region, proxyServer }); const credentialsClient = new CredentialsClient({ region, proxyServer });
let sourceAccountId: string;
// Always export the source credentials and account ID. let webIdentityToken: string;
// The STS client for calling AssumeRole pulls creds from the environment.
// Plus, in the assume role case, if the AssumeRole call fails, we want
// the source credentials and account ID to already be masked as secrets
// in any error messages.
if (AccessKeyId) {
if (!SecretAccessKey) {
throw new Error("'aws-secret-access-key' must be provided if 'aws-access-key-id' is provided");
}
exportCredentials({ AccessKeyId, SecretAccessKey, SessionToken });
}
// If OIDC is being used, generate token // If OIDC is being used, generate token
// Else, validate that the SDK can pick up credentials // Else, validate that the SDK can pick up credentials
let sourceAccountId: string;
let webIdentityToken: string;
if (useGitHubOIDCProvider()) { if (useGitHubOIDCProvider()) {
webIdentityToken = await core.getIDToken(audience); webIdentityToken = await core.getIDToken(audience);
// Implement #359 } else if (AccessKeyId) {
} else { if (!SecretAccessKey) {
// Regardless of whether any source credentials were provided as inputs, throw new Error("'aws-secret-access-key' must be provided if 'aws-access-key-id' is provided");
// validate that the SDK can actually pick up credentials. This validates }
// cases where this action is on a self-hosted runner that doesn't have credentials // The STS client for calling AssumeRole pulls creds from the environment.
// configured correctly, and cases where the user intended to provide input // Plus, in the assume role case, if the AssumeRole call fails, we want
// credentials but the secrets inputs resolved to empty strings. // the source credentials and account ID to already be masked as secrets
await credentialsClient.validateCredentials(AccessKeyId, roleChaining); // in any error messages.
exportCredentials({ AccessKeyId, SecretAccessKey, SessionToken });
} else if (!webIdentityTokenFile && !roleChaining) {
throw new Error('Could not determine how to assume credentials. Please check your inputs and try again.');
}
if (AccessKeyId || roleChaining) {
// Validate that the SDK can actually pick up credentials.
// This validates cases where this action is using existing environment credentials,
// and cases where the user intended to provide input credentials but the secrets inputs resolved to empty strings.
await credentialsClient.validateCredentials(AccessKeyId, roleChaining);
sourceAccountId = await exportAccountId(credentialsClient, maskAccountId); sourceAccountId = await exportAccountId(credentialsClient, maskAccountId);
} }
@@ -131,9 +125,7 @@ export async function run() {
await credentialsClient.validateCredentials(roleCredentials.Credentials?.AccessKeyId); await credentialsClient.validateCredentials(roleCredentials.Credentials?.AccessKeyId);
} }
await exportAccountId(credentialsClient, maskAccountId); await exportAccountId(credentialsClient, maskAccountId);
// implement #432
} else { } else {
// implement #370
core.info('Proceeding with IAM user credentials'); core.info('Proceeding with IAM user credentials');
} }
} catch (error) { } catch (error) {

View File

@@ -171,19 +171,6 @@ describe('Configure AWS Credentials', () => {
expect(core.setFailed).toHaveBeenCalledTimes(0); expect(core.setFailed).toHaveBeenCalledTimes(0);
}); });
test('hosted runners can pull creds from a self-hosted environment', async () => {
const mockInputs = { 'aws-region': FAKE_REGION };
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(mockInputs));
await run();
expect(mockedSTS.commandCalls(AssumeRoleCommand)).toHaveLength(0);
expect(core.exportVariable).toHaveBeenCalledTimes(2);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', FAKE_REGION);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', FAKE_REGION);
expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID);
});
test('action with no accessible credentials fails', async () => { test('action with no accessible credentials fails', async () => {
const mockInputs = { 'aws-region': FAKE_REGION }; const mockInputs = { 'aws-region': FAKE_REGION };
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(mockInputs)); jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(mockInputs));
@@ -195,7 +182,7 @@ describe('Configure AWS Credentials', () => {
await run(); await run();
expect(core.setFailed).toHaveBeenCalledWith( expect(core.setFailed).toHaveBeenCalledWith(
'Credentials could not be loaded, please check your action inputs: Could not load credentials from any providers' 'Could not determine how to assume credentials. Please check your inputs and try again.'
); );
}); });
@@ -210,7 +197,7 @@ describe('Configure AWS Credentials', () => {
await run(); await run();
expect(core.setFailed).toHaveBeenCalledWith( expect(core.setFailed).toHaveBeenCalledWith(
'Credentials could not be loaded, please check your action inputs: Access key ID empty after loading credentials' 'Could not determine how to assume credentials. Please check your inputs and try again.'
); );
}); });