Skip to content

feat: idempotent fetch #1289

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Feb 8, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 attempt to use existing credentials instead of fetching new credentials. 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: Set to true if you are using multiple workflows that use the same AWS Credentials. 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
10 changes: 10 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,13 @@ 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;
const identity = await client.send(new GetCallerIdentityCommand({}));
const accountId = identity.Account;
if (!accountId) {
return false;
}
return true;
}
12 changes: 12 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,7 @@ export async function run() {
const specialCharacterWorkaroundInput =
core.getInput('special-characters-workaround', { required: false }) || 'false';
const specialCharacterWorkaround = specialCharacterWorkaroundInput.toLowerCase() === 'true';
const useExistingCredentials = core.getInput('use-existing-credentials', { required: false }) || 'false';
let maxRetries = Number.parseInt(core.getInput('retry-max-attempts', { required: false })) || 12;
switch (true) {
case specialCharacterWorkaround:
Expand Down Expand Up @@ -116,6 +118,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 === 'true') {
const validCredentials = await areCredentialsValid(credentialsClient);
if (validCredentials) {
core.info('Pre-existing credentials are valid. No need to generate new ones.');
return;
}
core.info('No valid credentials exist. Running as normal.');
}

// If OIDC is being used, generate token
// Else, export credentials provided as input
if (useGitHubOIDCProvider()) {
Expand Down
18 changes: 18 additions & 0 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,5 +299,23 @@ describe('Configure AWS Credentials', {}, () => {
await run();
expect(core.setFailed).toHaveBeenCalled();
});
it('re-uses existing credentials if they are valid', {}, async () => {
vi.clearAllMocks();
mockedSTSClient.reset();
vi.spyOn(core, 'getInput').mockImplementation(mocks.getInput(mocks.USE_EXISTING_CREDENTIALS_INPUTS));
vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken');
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';

mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
mockedSTSClient.on(GetCallerIdentityCommand).resolvesOnce({ ...mocks.outputs.GET_CALLER_IDENTITY_NO_CREDS });
await run();
expect(core.info).toHaveBeenCalledWith('No valid credentials exist. Running as normal.')

mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });

mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
await run();
expect(core.info).toHaveBeenLastCalledWith('Pre-existing credentials are valid. No need to generate new ones.')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't expect on log messages if we can help it.

})
});
});
9 changes: 9 additions & 0 deletions test/mockinputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ const inputs = {
'role-chaining': 'true',
'aws-region': 'fake-region-1',
},
USE_EXISTING_CREDENTIALS_INPUTS: {
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
'aws-region': 'fake-region-1',
'special-characters-workaround': 'true',
'use-existing-credentials': 'true',
}
};

const envs = {
Expand Down Expand Up @@ -55,6 +61,9 @@ const outputs = {
GET_CALLER_IDENTITY: {
Account: '111111111111',
Arn: 'arn:aws:iam::111111111111:role/MY-ROLE',
},
GET_CALLER_IDENTITY_NO_CREDS: {

},
FAKE_STS_ACCESS_KEY_ID: 'STSAWSACCESSKEYID',
FAKE_STS_SECRET_ACCESS_KEY: 'STSAWSSECRETACCESSKEY',
Expand Down
Loading