diff --git a/dist/cleanup/src/helpers.d.ts b/dist/cleanup/src/helpers.d.ts index 9c81dc9..c6c90b8 100644 --- a/dist/cleanup/src/helpers.d.ts +++ b/dist/cleanup/src/helpers.d.ts @@ -2,7 +2,7 @@ import type { Credentials } from '@aws-sdk/client-sts'; import type { CredentialsClient } from './CredentialsClient'; export declare function exportCredentials(creds?: Partial): void; export declare function exportRegion(region: string): void; -export declare function exportAccountId(credentialsClient: CredentialsClient, maskAccountId?: string): Promise; +export declare function exportAccountId(credentialsClient: CredentialsClient, maskAccountId?: boolean): Promise; export declare function sanitizeGitHubVariables(name: string): string; export declare function defaultSleep(ms: number): Promise; declare let sleep: typeof defaultSleep; diff --git a/dist/index.js b/dist/index.js index 799675b..cdefa6e 100644 --- a/dist/index.js +++ b/dist/index.js @@ -149,7 +149,7 @@ async function assumeRoleWithCredentials(params, client) { } } 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 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) { @@ -188,7 +188,7 @@ async function assumeRole(params) { Tags: tags ? tags : undefined, ExternalId: roleExternalId ? roleExternalId : undefined, Policy: inlineSessionPolicy ? inlineSessionPolicy : undefined, - PolicyArns: managedSessionPolicies ? managedSessionPolicies : undefined, + PolicyArns: managedSessionPolicies?.length ? managedSessionPolicies : undefined, }; const keys = Object.keys(commonAssumeRoleParams); 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 roleToAssume = core.getInput('role-to-assume', { 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 webIdentityTokenFile = core.getInput('web-identity-token-file', { required: false }); 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 managedSessionPoliciesInput = core.getMultilineInput('managed-session-policies', { required: false }); const managedSessionPolicies = []; + const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false'; + const roleChaining = roleChainingInput.toLowerCase() === 'true'; for (const managedSessionPolicy of managedSessionPoliciesInput) { 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 const useGitHubOIDCProvider = () => { // 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 && !roleChaining); }; - // Validate and export region - if (region) { - if (!region.match(REGION_REGEX)) { - throw new Error(`Region is not valid: ${region}`); - } - (0, helpers_1.exportRegion)(region); + if (!region.match(REGION_REGEX)) { + throw new Error(`Region is not valid: ${region}`); } + (0, helpers_1.exportRegion)(region); // Instantiate credentials client const credentialsClient = new CredentialsClient_1.CredentialsClient({ region, proxyServer }); - // Always export the source credentials and account ID. - // 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) { + let sourceAccountId; + let webIdentityToken; + // If OIDC is being used, generate token + // Else, validate that the SDK can pick up credentials + if (useGitHubOIDCProvider()) { + webIdentityToken = await core.getIDToken(audience); + } + else if (AccessKeyId) { if (!SecretAccessKey) { 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 }); } - // If OIDC is being used, generate token - // Else, validate that the SDK can pick up credentials - let sourceAccountId; - let webIdentityToken; - if (useGitHubOIDCProvider()) { - webIdentityToken = await core.getIDToken(audience); - // Implement #359 + else if (!webIdentityTokenFile && !roleChaining) { + throw new Error('Could not determine how to assume credentials. Please check your inputs and try again.'); } - else { - // Regardless of whether any source credentials were provided as inputs, - // 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 - // configured correctly, and cases where the user intended to provide input - // credentials but the secrets inputs resolved to empty strings. + 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 (0, helpers_1.exportAccountId)(credentialsClient, maskAccountId); } @@ -491,10 +487,8 @@ async function run() { await credentialsClient.validateCredentials(roleCredentials.Credentials?.AccessKeyId); } await (0, helpers_1.exportAccountId)(credentialsClient, maskAccountId); - // implement #432 } else { - // implement #370 core.info('Proceeding with IAM user credentials'); } } diff --git a/src/index.ts b/src/index.ts index 68edc75..63b9aaf 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,11 +29,11 @@ export async function run() { const inlineSessionPolicy = core.getInput('inline-session-policy', { required: false }); const managedSessionPoliciesInput = core.getMultilineInput('managed-session-policies', { required: false }); const managedSessionPolicies: any[] = []; + const roleChainingInput = core.getInput('role-chaining', { required: false }) || 'false'; + const roleChaining = roleChainingInput.toLowerCase() === 'true'; for (const managedSessionPolicy of managedSessionPoliciesInput) { 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 const useGitHubOIDCProvider = () => { @@ -63,44 +63,38 @@ export async function run() { ); }; - // Validate and export region - if (region) { - if (!region.match(REGION_REGEX)) { - throw new Error(`Region is not valid: ${region}`); - } - exportRegion(region); + if (!region.match(REGION_REGEX)) { + throw new Error(`Region is not valid: ${region}`); } + exportRegion(region); // Instantiate credentials client const credentialsClient = new CredentialsClient({ region, proxyServer }); - - // Always export the source credentials and account ID. - // 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 }); - } + let sourceAccountId: string; + let webIdentityToken: string; // If OIDC is being used, generate token // Else, validate that the SDK can pick up credentials - let sourceAccountId: string; - let webIdentityToken: string; if (useGitHubOIDCProvider()) { webIdentityToken = await core.getIDToken(audience); - // Implement #359 - } else { - // Regardless of whether any source credentials were provided as inputs, - // 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 - // 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); + } else if (AccessKeyId) { + if (!SecretAccessKey) { + 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. + 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); } @@ -131,9 +125,7 @@ export async function run() { await credentialsClient.validateCredentials(roleCredentials.Credentials?.AccessKeyId); } await exportAccountId(credentialsClient, maskAccountId); - // implement #432 } else { - // implement #370 core.info('Proceeding with IAM user credentials'); } } catch (error) { diff --git a/test/index.test.ts b/test/index.test.ts index b9a52dd..51415c2 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -171,19 +171,6 @@ describe('Configure AWS Credentials', () => { 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 () => { const mockInputs = { 'aws-region': FAKE_REGION }; jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(mockInputs)); @@ -195,7 +182,7 @@ describe('Configure AWS Credentials', () => { await run(); 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(); 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.' ); });