diff --git a/action.yml b/action.yml index a0be61e..f4cff80 100644 --- a/action.yml +++ b/action.yml @@ -16,35 +16,35 @@ inputs: description: The Amazon Resource Name (ARN) of the role to assume. Use the provided credentials to assume an IAM role and configure the Actions environment with the assumed role credentials rather than with the provided credentials. required: false aws-access-key-id: - description: AWS Access Key ID. This input is required if running in the GitHub hosted environment. It is optional if running in a self-hosted environment that already has AWS credentials, for example on an EC2 instance. + description: AWS Access Key ID. Provide this key if you want to assume a role using access keys rather than a web identity token. required: false aws-secret-access-key: - description: AWS Access Key ID. This input is required if running in the GitHub hosted environment. It is optional if running in a self-hosted environment that already has AWS credentials, for example on an EC2 instance. + description: AWS Secret Access Key. Required if aws-access-key-id is provided. required: false aws-session-token: - description: AWS Session Token + description: AWS Session Token. required: false web-identity-token-file: description: Use the web identity token file from the provided file system path in order to assume an IAM role using a web identity, e.g. from within an Amazon EKS worker node. required: false role-chaining: - description: 'Use existing credentials from the environment to assume a new role' + description: Use existing credentials from the environment to assume a new role, rather than providing credentials as input. required: false audience: description: The audience to use for the OIDC provider required: false default: sts.amazonaws.com http-proxy: - description: 'Proxy to use for the AWS SDK agent' + description: Proxy to use for the AWS SDK agent required: false mask-aws-account-id: - description: Whether to mask the AWS account ID for these credentials as a secret value, so that it is masked in logs. Valid values are "true" or "false". Defaults to "true". + description: Whether to mask the AWS account ID for these credentials as a secret value. By default the account ID will not be masked required: false role-duration-seconds: - description: "Role duration in seconds (default: 6 hours, 1 hour for OIDC/specified aws-session-token)" + description: Role duration in seconds. Default is one hour. required: false role-external-id: - description: The external ID of the role to assume + description: The external ID of the role to assume. required: false role-session-name: description: "Role session name (default: GitHubActions)" @@ -53,23 +53,29 @@ inputs: description: Skip session tagging during role assumption required: false inline-session-policy: - description: 'Inline session policy' + description: Define an inline session policy to use when assuming a role required: false managed-session-policies: - description: 'List of managed session policies' + description: Define a list of managed session policies to use when assuming a role required: false output-credentials: description: Whether to set credentials as step output required: false unset-current-credentials: - description: Whether to unset the existing credentials in your runner + description: Whether to unset the existing credentials in your runner. May be useful if you run this action multiple times in the same job required: false disable-retry: - description: Whether to disable the retry and backoff mechanism when the assume role call fails + description: Whether to disable the retry and backoff mechanism when the assume role call fails. By default the retry mechanism is enabled required: false retry-max-attempts: - description: The maximum number of attempts it will attempt to retry the assume role call + description: The maximum number of attempts it will attempt to retry the assume role call. By default it will retry 12 times required: false outputs: aws-account-id: description: The AWS account ID for the provided credentials + aws-access-key-id: + description: The AWS access key ID for the provided credentials + aws-secret-access-key: + description: The AWS secret access key for the provided credentials + aws-session-token: + description: The AWS session token for the provided credentials diff --git a/dist/index.js b/dist/index.js index 6627b32..e141926 100644 --- a/dist/index.js +++ b/dist/index.js @@ -486,7 +486,7 @@ async function run() { let sourceAccountId; let webIdentityToken; // If OIDC is being used, generate token - // Else, validate that the SDK can pick up credentials + // Else, export credentials provided as input if (useGitHubOIDCProvider()) { try { webIdentityToken = await (0, helpers_1.retryAndBackoff)(async () => { @@ -503,7 +503,7 @@ async function run() { } // 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 + // the source credentials to already be masked as secrets // in any error messages. (0, helpers_1.exportCredentials)({ AccessKeyId, SecretAccessKey, SessionToken }); } diff --git a/src/index.ts b/src/index.ts index 30c34f4..e4ecf48 100644 --- a/src/index.ts +++ b/src/index.ts @@ -90,7 +90,7 @@ export async function run() { let webIdentityToken: string; // If OIDC is being used, generate token - // Else, validate that the SDK can pick up credentials + // Else, export credentials provided as input if (useGitHubOIDCProvider()) { try { webIdentityToken = await retryAndBackoff( @@ -109,7 +109,7 @@ export async function run() { } // 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 + // the source credentials to already be masked as secrets // in any error messages. exportCredentials({ AccessKeyId, SecretAccessKey, SessionToken }); } else if (!webIdentityTokenFile && !roleChaining) { diff --git a/test/index.test.ts b/test/index.test.ts index 04aae16..0b6cf4d 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -18,6 +18,7 @@ const FAKE_SESSION_TOKEN = 'MYAWSSESSIONTOKEN'; const FAKE_STS_ACCESS_KEY_ID = 'STSAWSACCESSKEYID'; const FAKE_STS_SECRET_ACCESS_KEY = 'STSAWSSECRETACCESSKEY'; const FAKE_STS_SESSION_TOKEN = 'STSAWSSESSIONTOKEN'; +const FAKE_ASSUMED_ROLE_ID = 'AROAFAKEASSUMEDROLEID'; const FAKE_REGION = 'fake-region-1'; const FAKE_ACCOUNT_ID = '123456789012'; const FAKE_ROLE_ACCOUNT_ID = '111111111111'; @@ -46,7 +47,6 @@ const DEFAULT_INPUTS = { ...CREDS_INPUTS, 'aws-session-token': FAKE_SESSION_TOKEN, 'aws-region': FAKE_REGION, - 'mask-aws-account-id': 'true', }; const ASSUME_ROLE_INPUTS = { ...CREDS_INPUTS, 'role-to-assume': ROLE_ARN, 'aws-region': FAKE_REGION }; // #endregion @@ -102,6 +102,9 @@ describe('Configure AWS Credentials', () => { jest.spyOn(core, 'setOutput').mockImplementation(); jest.spyOn(core, 'setFailed').mockImplementation(); jest.spyOn(core, 'debug').mockImplementation(); + jest.spyOn(core, 'info').mockImplementation((string) => { + return string; + }); (fromEnv as jest.Mock) .mockImplementationOnce(() => () => ({ accessKeyId: FAKE_ACCESS_KEY_ID, @@ -122,6 +125,10 @@ describe('Configure AWS Credentials', () => { SessionToken: FAKE_STS_SESSION_TOKEN, Expiration: new Date(8640000000000000), }, + AssumedRoleUser: { + AssumedRoleId: FAKE_ASSUMED_ROLE_ID, + Arn: ROLE_ARN, + }, }); mockedSTS.on(AssumeRoleWithWebIdentityCommand).resolves({ Credentials: { @@ -130,6 +137,10 @@ describe('Configure AWS Credentials', () => { SessionToken: FAKE_STS_SESSION_TOKEN, Expiration: new Date(8640000000000000), }, + AssumedRoleUser: { + AssumedRoleId: FAKE_ASSUMED_ROLE_ID, + Arn: ROLE_ARN, + }, }); withsleep(async () => { return Promise.resolve(); @@ -148,7 +159,7 @@ describe('Configure AWS Credentials', () => { expect(mockedSTS.commandCalls(AssumeRoleCommand)).toHaveLength(0); expect(core.exportVariable).toHaveBeenCalledTimes(5); - expect(core.setSecret).toHaveBeenCalledTimes(4); + expect(core.setSecret).toHaveBeenCalledTimes(3); expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID); expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCESS_KEY_ID); expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY); @@ -299,6 +310,7 @@ describe('Configure AWS Credentials', () => { expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'us-east-1'); expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'us-east-1'); expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID); + expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCOUNT_ID); expect(core.setSecret).toHaveBeenCalledTimes(3); }); @@ -495,6 +507,25 @@ describe('Configure AWS Credentials', () => { }); }); + test('GH OIDC check fails if token is not set', async () => { + process.env['GITHUB_ACTIONS'] = 'true'; + jest.spyOn(core, 'getInput').mockImplementation( + mockGetInput({ + 'role-to-assume': ROLE_ARN, + 'aws-region': FAKE_REGION, + }) + ); + + await run(); + + expect(core.info).toHaveBeenCalledWith( + 'It looks like you might be trying to authenticate with OIDC. Did you mean to set the `id-token` permission?' + ); + expect(core.setFailed).toHaveBeenCalledWith( + 'Could not determine how to assume credentials. Please check your inputs and try again.' + ); + }); + test('role assumption fails after maximum trials using OIDC provider', async () => { process.env['GITHUB_ACTIONS'] = 'true'; process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN'] = 'test-token'; @@ -592,12 +623,13 @@ describe('Configure AWS Credentials', () => { 'retry-max-attempts': '15', }) ); - mockedSTS.reset(); mockedSTS.on(AssumeRoleWithWebIdentityCommand).rejects(); await run(); + expect(mockedSTS.commandCalls(AssumeRoleWithWebIdentityCommand).length).toEqual(15); + expect(core.setFailed).toHaveBeenCalledWith('Could not assume role with OIDC: '); }); test('role external ID provided', async () => { @@ -766,6 +798,14 @@ describe('Configure AWS Credentials', () => { }); }); + test('prints assumed role id', async () => { + jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS)); + + await run(); + + expect(core.info).toHaveBeenCalledWith(`Authenticated as assumedRoleId ${FAKE_ASSUMED_ROLE_ID}`); + }); + test('unsets credentials if enabled', async () => { jest .spyOn(core, 'getInput') @@ -773,6 +813,16 @@ describe('Configure AWS Credentials', () => { await run(); - expect(core.exportVariable).toHaveBeenCalledTimes(9); + expect(core.exportVariable).toHaveBeenCalledTimes(12); + }); + + test('sets credentials as output if enabled', async () => { + jest + .spyOn(core, 'getInput') + .mockImplementation(mockGetInput({ ...ASSUME_ROLE_INPUTS, 'output-credentials': 'true' })); + + await run(); + + expect(core.setOutput).toHaveBeenCalledTimes(4); }); });