Skip to content

Commit

Permalink
feat: idempotent fetch (#1289)
Browse files Browse the repository at this point in the history
* Add functionality to re-use existing credentials

* Finish adding use-existing-credentials logic

* Add testing for use-existing-credentials

* Update README

* feat: finalize use-exisiting-credentials feature

---------

Co-authored-by: Tom Keller <[email protected]>
  • Loading branch information
lehmanmj and kellertk authored Feb 8, 2025
1 parent 3478c15 commit eb70354
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**.
Expand Down
2 changes: 2 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,16 @@ export function isDefined<T>(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;
}
}
13 changes: 13 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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()) {
Expand Down
13 changes: 13 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -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();
})
});
});
5 changes: 5 additions & 0 deletions test/mockinputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down

0 comments on commit eb70354

Please sign in to comment.