mirror of
https://github.com/aws-actions/configure-aws-credentials.git
synced 2026-09-03 06:05:04 +09:00
feat: Support usage of AWS Profiles (#1696)
* Support usage of AWS Profiles
* squash merge main updates w feature branch
Squashed commit of the following:
commit ef2df4679f908ff30d5a711258ace2fa906c4bf3
Author: Michael Lehmann <lehmanmj@amazon.com>
Date: Tue Mar 17 11:24:04 2026 -0700
dist update
commit db3779a0e9
Author: Jan Feddern <jf@novatec-gmbh.de>
Date: Sun Dec 21 11:28:36 2025 +0100
Support usage of AWS Profiles
* chore: Update dist
* consistent outputEnvCredentials
* take out tests temporarily
* chore: Update dist
* debug changes for static creds
* remove debug and only cleanup profile if it was set
* formatting fixes + remove profile from cleanup test
* feat: Support usage of AWS Profiles
Adds a config option to support writing to profile files instead of
exporting environment variables.
Closes #1594. Closes #1586. Closes #112.
* chore: fix failing test case and windows path
* chore: lint project markdown files
* chore: update scripts in package.json and tsconfig update
* make env vars consistent, readme linting
* debug for profile path env vars
* remove debug
* remove profile backups
* error if we try to overwrite
* add option to overwrite existing profiles
* tests for overwrite option
* default to no env vars
* remove default from action file
* add static credential env var support
* validation fix for static creds multi profile
* debug sleep for static creds validation
* wait syntax
* undo sleep for creds validate
* test coverage, readme/action yml updates, validate creds later on self-hosted runner
* security dependency updates
* chore(deps-dev): bump @biomejs/biome from 2.4.8 to 2.4.10 (#1709)
Bumps [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) from 2.4.8 to 2.4.10.
- [Release notes](https://github.com/biomejs/biome/releases)
- [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md)
- [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.4.10/packages/@biomejs/biome)
---
updated-dependencies:
- dependency-name: "@biomejs/biome"
dependency-version: 2.4.10
dependency-type: direct:development
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore(deps-dev): bump @aws-sdk/credential-provider-env (#1713)
Bumps [@aws-sdk/credential-provider-env](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages-internal/credential-provider-env) from 3.972.22 to 3.972.24.
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages-internal/credential-provider-env/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/HEAD/packages-internal/credential-provider-env)
---
updated-dependencies:
- dependency-name: "@aws-sdk/credential-provider-env"
dependency-version: 3.972.24
dependency-type: direct:development
update-type: version-update:semver-patch
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore: Update dist
* chore(deps): bump @aws-sdk/client-sts from 3.1015.0 to 3.1020.0 (#1710)
Bumps [@aws-sdk/client-sts](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-sts) from 3.1015.0 to 3.1020.0.
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-sts/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1020.0/clients/client-sts)
---
updated-dependencies:
- dependency-name: "@aws-sdk/client-sts"
dependency-version: 3.1020.0
dependency-type: direct:production
update-type: version-update:semver-minor
...
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
* chore: Update dist
* fix: do not write empty profile files
Also cleanup fix, additional test, README typo cleanup
* linting fix
* chore: linting fix
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Jan Feddern <jf@novatec-gmbh.de>
Co-authored-by: Michael Lehmann <lehmanmj@amazon.com>
Co-authored-by: GitHub Actions <github-aws-sdk-osds-automation@amazon.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
@@ -10,6 +10,7 @@ import { fs, vol } from 'memfs';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { CredentialsClient } from '../src/CredentialsClient';
|
||||
import { run } from '../src/index';
|
||||
import * as profileManager from '../src/profileManager';
|
||||
import mocks from './mockinputs.test';
|
||||
|
||||
const mockedSTSClient = mockClient(STSClient);
|
||||
@@ -822,4 +823,202 @@ describe('Configure AWS Credentials', {}, () => {
|
||||
expect(core.setFailed).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('AWS Profile Support', {}, () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mockedSTSClient.reset();
|
||||
vi.mock('node:fs');
|
||||
vol.reset();
|
||||
});
|
||||
|
||||
it('writes profile files with OIDC authentication', async () => {
|
||||
vi.spyOn(core, 'getInput').mockImplementation(
|
||||
mocks.getInput({
|
||||
...mocks.GH_OIDC_INPUTS,
|
||||
'aws-profile': 'dev',
|
||||
}),
|
||||
);
|
||||
vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken');
|
||||
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolves(mocks.outputs.STS_CREDENTIALS);
|
||||
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';
|
||||
|
||||
await run();
|
||||
|
||||
// Verify credentials were NOT exported to environment variables
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_SESSION_TOKEN', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_PROFILE', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_REGION', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_DEFAULT_REGION', expect.anything());
|
||||
|
||||
// Verify profile files were written
|
||||
expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: dev');
|
||||
expect(core.info).toHaveBeenCalledWith('Writing config to profile: dev');
|
||||
expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: dev');
|
||||
expect(core.setFailed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('writes profile files with IAM user credentials', async () => {
|
||||
vi.spyOn(core, 'getInput').mockImplementation(
|
||||
mocks.getInput({
|
||||
...mocks.IAM_USER_INPUTS,
|
||||
'aws-profile': 'production',
|
||||
}),
|
||||
);
|
||||
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||
// biome-ignore lint/suspicious/noExplicitAny: any required to mock private method
|
||||
vi.spyOn(CredentialsClient.prototype as any, 'loadCredentials').mockResolvedValue({
|
||||
accessKeyId: 'MYAWSACCESSKEYID',
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
// Verify credentials were NOT exported to environment variables
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_SESSION_TOKEN', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_PROFILE', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_REGION', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_DEFAULT_REGION', expect.anything());
|
||||
|
||||
// Verify profile files were written
|
||||
expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: production');
|
||||
expect(core.info).toHaveBeenCalledWith('Writing config to profile: production');
|
||||
expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: production');
|
||||
expect(core.setFailed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('writes profile files with IAM user role assumption', async () => {
|
||||
vi.spyOn(core, 'getInput').mockImplementation(
|
||||
mocks.getInput({
|
||||
...mocks.IAM_ASSUMEROLE_INPUTS,
|
||||
'aws-profile': 'assumed-role',
|
||||
}),
|
||||
);
|
||||
mockedSTSClient.on(AssumeRoleCommand).resolves(mocks.outputs.STS_CREDENTIALS);
|
||||
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||
// biome-ignore lint/suspicious/noExplicitAny: any required to mock private method
|
||||
vi.spyOn(CredentialsClient.prototype as any, 'loadCredentials')
|
||||
.mockResolvedValueOnce({ accessKeyId: 'MYAWSACCESSKEYID' })
|
||||
.mockResolvedValueOnce({ accessKeyId: 'STSAWSACCESSKEYID' });
|
||||
|
||||
vi.spyOn(profileManager, 'writeProfileFiles');
|
||||
await run();
|
||||
|
||||
// Verify credentials were NOT exported to environment variables
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_SESSION_TOKEN', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_PROFILE', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_REGION', expect.anything());
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_DEFAULT_REGION', expect.anything());
|
||||
|
||||
// Verify profile files were written
|
||||
expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: assumed-role');
|
||||
expect(core.info).toHaveBeenCalledWith('Writing config to profile: assumed-role');
|
||||
expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: assumed-role');
|
||||
|
||||
// Verify profile files were written twice (first to write access key id and access key, second to write
|
||||
// actual session token after role assumption
|
||||
expect(profileManager.writeProfileFiles).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(core.setFailed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('respects output-env-credentials=true with profiles', async () => {
|
||||
vi.spyOn(core, 'getInput').mockImplementation(
|
||||
mocks.getInput({
|
||||
...mocks.GH_OIDC_INPUTS,
|
||||
'aws-profile': 'dev',
|
||||
'output-env-credentials': 'true',
|
||||
}),
|
||||
);
|
||||
vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken');
|
||||
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolves(mocks.outputs.STS_CREDENTIALS);
|
||||
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';
|
||||
|
||||
await run();
|
||||
|
||||
// verify that env vars were exported
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', 'STSAWSACCESSKEYID');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', 'STSAWSSECRETACCESSKEY');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', 'STSAWSSESSIONTOKEN');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_PROFILE', 'dev');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'fake-region-1');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'fake-region-1');
|
||||
|
||||
// Verify profile files were still written
|
||||
expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: dev');
|
||||
expect(core.info).toHaveBeenCalledWith('Writing config to profile: dev');
|
||||
expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: dev');
|
||||
expect(core.setFailed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('maintains backward compatibility when aws-profile is not specified', async () => {
|
||||
vi.spyOn(core, 'getInput').mockImplementation(mocks.getInput(mocks.GH_OIDC_INPUTS));
|
||||
vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken');
|
||||
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolves(mocks.outputs.STS_CREDENTIALS);
|
||||
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';
|
||||
|
||||
await run();
|
||||
|
||||
// Verify credentials WERE exported to environment variables (backward compatibility)
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', 'STSAWSACCESSKEYID');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', 'STSAWSSECRETACCESSKEY');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', 'STSAWSSESSIONTOKEN');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'fake-region-1');
|
||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'fake-region-1');
|
||||
|
||||
// Verify AWS_PROFILE was NOT exported
|
||||
expect(core.exportVariable).not.toHaveBeenCalledWith('AWS_PROFILE', expect.anything());
|
||||
|
||||
// Verify profile files were NOT written
|
||||
expect(core.info).not.toHaveBeenCalledWith(expect.stringContaining('Writing credentials to profile'));
|
||||
expect(core.info).not.toHaveBeenCalledWith(expect.stringContaining('✓ Successfully configured AWS profile:'));
|
||||
expect(core.setFailed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('handles default profile correctly', async () => {
|
||||
vi.spyOn(core, 'getInput').mockImplementation(
|
||||
mocks.getInput({
|
||||
...mocks.GH_OIDC_INPUTS,
|
||||
'aws-profile': 'default',
|
||||
}),
|
||||
);
|
||||
vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken');
|
||||
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolves(mocks.outputs.STS_CREDENTIALS);
|
||||
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';
|
||||
|
||||
await run();
|
||||
|
||||
// Verify profile files were written for 'default' profile
|
||||
expect(core.info).toHaveBeenCalledWith('Writing credentials to profile: default');
|
||||
expect(core.info).toHaveBeenCalledWith('Writing config to profile: default');
|
||||
expect(core.info).toHaveBeenCalledWith('✓ Successfully configured AWS profile: default');
|
||||
expect(core.setFailed).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects invalid profile names with whitespace', async () => {
|
||||
vi.spyOn(core, 'getInput').mockImplementation(
|
||||
mocks.getInput({
|
||||
...mocks.GH_OIDC_INPUTS,
|
||||
'aws-profile': 'invalid profile',
|
||||
}),
|
||||
);
|
||||
vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken');
|
||||
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolves(mocks.outputs.STS_CREDENTIALS);
|
||||
mockedSTSClient.on(GetCallerIdentityCommand).resolves({ ...mocks.outputs.GET_CALLER_IDENTITY });
|
||||
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';
|
||||
|
||||
await run();
|
||||
|
||||
expect(core.setFailed).toHaveBeenCalledWith(expect.stringContaining('whitespace'));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user