diff --git a/README.md b/README.md index 03f7a43e2..1e7f7c29b 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,7 @@ See [action.yml](./action.yml) for more detail. | disable-retry | Disabled retry/backoff logic for assume role calls. By default, retries are enabled. | No | | retry-max-attempts | Limits the number of retry attempts before giving up. Defaults to 12. | No | | special-characters-workaround | Uncommonly, some environments cannot tolerate special characters in a secret key. This option will retry fetching credentials until the secret access key does not contain special characters. This option overrides disable-retry and retry-max-attempts. | No | +| use-existing-credentials | When set, the action will check if existing credentials are valid and exit if they are. Defaults to false. | No | #### Credential Lifetime The default session duration is **1 hour**. diff --git a/action.yml b/action.yml index 7e98591a9..91ba47548 100644 --- a/action.yml +++ b/action.yml @@ -73,6 +73,8 @@ inputs: special-characters-workaround: description: Some environments do not support special characters in AWS_SECRET_ACCESS_KEY. This option will retry fetching credentials until the secret access key does not contain special characters. This option overrides disable-retry and retry-max-attempts. This option is disabled by default required: false + use-existing-credentials: + description: When enabled, this option will check if there are already valid credentials in the environment. If there are, new credentials will not be fetched. If there are not, the action will run as normal. outputs: aws-account-id: description: The AWS account ID for the provided credentials diff --git a/src/helpers.ts b/src/helpers.ts index e27059649..2bc87d02a 100644 --- a/src/helpers.ts +++ b/src/helpers.ts @@ -144,3 +144,16 @@ export function isDefined(i: T | undefined | null): i is T { return i !== undefined && i !== null; } /* c8 ignore stop */ + +export async function areCredentialsValid(credentialsClient: CredentialsClient) { + const client = credentialsClient.stsClient; + try { + const identity = await client.send(new GetCallerIdentityCommand({})); + if (identity.Account) { + return true; + } + return false; + } catch (_) { + return false; + } +} diff --git a/src/index.ts b/src/index.ts index 46272e019..a35452bf4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import type { AssumeRoleCommandOutput } from '@aws-sdk/client-sts'; import { CredentialsClient } from './CredentialsClient'; import { assumeRole } from './assumeRole'; import { + areCredentialsValid, errorMessage, exportAccountId, exportCredentials, @@ -60,6 +61,8 @@ export async function run() { const specialCharacterWorkaroundInput = core.getInput('special-characters-workaround', { required: false }) || 'false'; const specialCharacterWorkaround = specialCharacterWorkaroundInput.toLowerCase() === 'true'; + const useExistingCredentialsInput = core.getInput('use-existing-credentials', { required: false }) || 'false'; + const useExistingCredentials = useExistingCredentialsInput.toLowerCase() === 'true'; let maxRetries = Number.parseInt(core.getInput('retry-max-attempts', { required: false })) || 12; switch (true) { case specialCharacterWorkaround: @@ -116,6 +119,16 @@ export async function run() { let sourceAccountId: string; let webIdentityToken: string; + //if the user wants to attempt to use existing credentials, check if we have some already + if (useExistingCredentials) { + const validCredentials = await areCredentialsValid(credentialsClient); + if (validCredentials) { + core.notice('Pre-existing credentials are valid. No need to generate new ones.'); + return; + } + core.notice('No valid credentials exist. Running as normal.'); + } + // If OIDC is being used, generate token // Else, export credentials provided as input if (useGitHubOIDCProvider()) { diff --git a/test/index.test.ts b/test/index.test.ts index e65b5071f..a95443da6 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -27,6 +27,7 @@ describe('Configure AWS Credentials', {}, () => { vi.spyOn(core, 'setOutput').mockImplementation((_n, _v) => {}); vi.spyOn(core, 'debug').mockImplementation((_m) => {}); vi.spyOn(core, 'info').mockImplementation((_m) => {}); + vi.spyOn(core, 'notice').mockImplementation((_m) => {}); // Remove any existing environment variables before each test to prevent the // SDK from picking them up process.env = { ...mocks.envs }; @@ -299,5 +300,17 @@ describe('Configure AWS Credentials', {}, () => { await run(); expect(core.setFailed).toHaveBeenCalled(); }); + it('gets new creds if told to reuse existing but they\'re invalid', {}, async () => { + vi.spyOn(core, 'getInput').mockImplementation(mocks.getInput(mocks.USE_EXISTING_CREDENTIALS_INPUTS)); + mockedSTSClient.on(GetCallerIdentityCommand).rejects(); + await run(); + expect(core.notice).toHaveBeenCalledWith('No valid credentials exist. Running as normal.') + }); + it('doesn\'t get new creds if there are already valid ones and we said use them', {}, async () => { + vi.spyOn(core, 'getInput').mockImplementation(mocks.getInput(mocks.USE_EXISTING_CREDENTIALS_INPUTS)); + mockedSTSClient.on(GetCallerIdentityCommand).resolves(mocks.outputs.GET_CALLER_IDENTITY); + await run(); + expect(core.setFailed).not.toHaveBeenCalled(); + }) }); }); diff --git a/test/mockinputs.test.ts b/test/mockinputs.test.ts index 7773a92cc..c5908a818 100644 --- a/test/mockinputs.test.ts +++ b/test/mockinputs.test.ts @@ -27,6 +27,11 @@ const inputs = { 'role-chaining': 'true', 'aws-region': 'fake-region-1', }, + USE_EXISTING_CREDENTIALS_INPUTS: { + 'aws-region': 'fake-region-1', + 'use-existing-credentials': 'true', + 'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE', + } }; const envs = {