Merge pull request #1379 from aws-actions/disable-env-output

feat: Optional environment variable output
This commit is contained in:
Michael Lehmann
2025-06-05 15:06:53 -07:00
committed by GitHub
11 changed files with 2497 additions and 6376 deletions
+2 -1
View File
@@ -111,7 +111,8 @@ See [action.yml](./action.yml) for more detail.
| role-skip-session-tagging | Skips session tagging if set. | No |
| inline-session-policy | You may further restrict the assumed role policy by defining an inline policy here. | No |
| managed-session-policies | You may further restrict the assumed role policy by specifying a managed policy here. | No |
| output-credentials | When set, outputs fetched credentials as action step output. (Outputs access-key-id, secret-access-key, session-token, and expiration). Defaults to false. | No |
| output-credentials | When set, outputs fetched credentials as action step output. (Outputs access-key-id, secret-access-key, session-token, and expiration). Defaults to false. | No |
| output-env-credentials | When set, outputs fetched credentials as environment variables (AWS_REGION, AWS_DEFAULT_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN). Defaults to true. Set to false if you need to avoid setting/changing env variables. You'd probably want to use output-credentials if you disable this. (NOTE: Setting to false will prevent the aws-account-id from being exported as a step output). | No |
| unset-current-credentials | When set, attempts to unset any existing credentials in your action runner. | No |
| 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 |
+6
View File
@@ -61,6 +61,10 @@ inputs:
output-credentials:
description: Whether to set credentials as step output
required: false
output-env-credentials:
description: Whether to export credentials as environment variables. If you set this to false, you probably want to use output-credentials.
required: false
default: true
unset-current-credentials:
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
@@ -84,3 +88,5 @@ outputs:
description: The AWS secret access key for the provided credentials
aws-session-token:
description: The AWS session token for the provided credentials
aws-expiration:
description: The expiration time for the provided credentials
Generated Vendored
+1174 -3145
View File
File diff suppressed because it is too large Load Diff
Generated Vendored
+1207 -3196
View File
File diff suppressed because it is too large Load Diff
+15 -12
View File
@@ -13,18 +13,21 @@ import { errorMessage } from '../helpers';
*/
export function cleanup() {
try {
// The GitHub Actions toolkit does not have an option to completely unset
// environment variables, so we overwrite the current value with an empty
// string. The AWS CLI and AWS SDKs will behave correctly: they treat an
// empty string value as if the environment variable does not exist.
core.exportVariable('AWS_ACCESS_KEY_ID', '');
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
core.exportVariable('AWS_SESSION_TOKEN', '');
core.exportVariable('AWS_DEFAULT_REGION', '');
core.exportVariable('AWS_REGION', '');
} catch (error) {
core.setFailed(errorMessage(error));
const outputEnvCredentialsInput = core.getInput('output-env-credentials', { required: false }) || 'true';
if (outputEnvCredentialsInput === 'true') {
try {
// The GitHub Actions toolkit does not have an option to completely unset
// environment variables, so we overwrite the current value with an empty
// string. The AWS CLI and AWS SDKs will behave correctly: they treat an
// empty string value as if the environment variable does not exist.
core.exportVariable('AWS_ACCESS_KEY_ID', '');
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
core.exportVariable('AWS_SESSION_TOKEN', '');
core.exportVariable('AWS_DEFAULT_REGION', '');
core.exportVariable('AWS_REGION', '');
} catch (error) {
core.setFailed(errorMessage(error));
}
}
}
/* c8 ignore start */
+35 -16
View File
@@ -39,23 +39,38 @@ export function translateEnvVariables() {
// Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets.
// Setting the credentials as secrets masks them in Github Actions logs
export function exportCredentials(creds?: Partial<Credentials>, outputCredentials?: boolean) {
export function exportCredentials(
creds?: Partial<Credentials>,
outputCredentials?: boolean,
outputEnvCredentials?: boolean,
) {
if (creds?.AccessKeyId) {
core.setSecret(creds.AccessKeyId);
core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId);
}
if (creds?.SecretAccessKey) {
core.setSecret(creds.SecretAccessKey);
core.exportVariable('AWS_SECRET_ACCESS_KEY', creds.SecretAccessKey);
}
if (creds?.SessionToken) {
core.setSecret(creds.SessionToken);
core.exportVariable('AWS_SESSION_TOKEN', creds.SessionToken);
} else if (process.env.AWS_SESSION_TOKEN) {
// clear session token from previous credentials action
core.exportVariable('AWS_SESSION_TOKEN', '');
}
if (outputEnvCredentials) {
if (creds?.AccessKeyId) {
core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId);
}
if (creds?.SecretAccessKey) {
core.exportVariable('AWS_SECRET_ACCESS_KEY', creds.SecretAccessKey);
}
if (creds?.SessionToken) {
core.exportVariable('AWS_SESSION_TOKEN', creds.SessionToken);
} else if (process.env.AWS_SESSION_TOKEN) {
// clear session token from previous credentials action
core.exportVariable('AWS_SESSION_TOKEN', '');
}
}
if (outputCredentials) {
@@ -74,17 +89,21 @@ export function exportCredentials(creds?: Partial<Credentials>, outputCredential
}
}
export function unsetCredentials() {
core.exportVariable('AWS_ACCESS_KEY_ID', '');
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
core.exportVariable('AWS_SESSION_TOKEN', '');
core.exportVariable('AWS_REGION', '');
core.exportVariable('AWS_DEFAULT_REGION', '');
export function unsetCredentials(outputEnvCredentials?: boolean) {
if (outputEnvCredentials) {
core.exportVariable('AWS_ACCESS_KEY_ID', '');
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
core.exportVariable('AWS_SESSION_TOKEN', '');
core.exportVariable('AWS_REGION', '');
core.exportVariable('AWS_DEFAULT_REGION', '');
}
}
export function exportRegion(region: string) {
core.exportVariable('AWS_DEFAULT_REGION', region);
core.exportVariable('AWS_REGION', region);
export function exportRegion(region: string, outputEnvCredentials?: boolean) {
if (outputEnvCredentials) {
core.exportVariable('AWS_DEFAULT_REGION', region);
core.exportVariable('AWS_REGION', region);
}
}
// Obtains account ID from STS Client and sets it as output
+9 -5
View File
@@ -56,6 +56,8 @@ export async function run() {
const roleChaining = roleChainingInput.toLowerCase() === 'true';
const outputCredentialsInput = core.getInput('output-credentials', { required: false }) || 'false';
const outputCredentials = outputCredentialsInput.toLowerCase() === 'true';
const outputEnvCredentialsInput = core.getInput('output-env-credentials', { required: false }) || 'true';
const outputEnvCredentials = outputEnvCredentialsInput.toLowerCase() === 'true';
const unsetCurrentCredentialsInput = core.getInput('unset-current-credentials', { required: false }) || 'false';
const unsetCurrentCredentials = unsetCurrentCredentialsInput.toLowerCase() === 'true';
const disableRetryInput = core.getInput('disable-retry', { required: false }) || 'false';
@@ -108,13 +110,13 @@ export async function run() {
};
if (unsetCurrentCredentials) {
unsetCredentials();
unsetCredentials(outputEnvCredentials);
}
if (!region.match(REGION_REGEX)) {
throw new Error(`Region is not valid: ${region}`);
}
exportRegion(region);
exportRegion(region, outputEnvCredentials);
// Instantiate credentials client
const credentialsClient = new CredentialsClient({ region, proxyServer });
@@ -153,7 +155,7 @@ export async function run() {
// Plus, in the assume role case, if the AssumeRole call fails, we want
// the source credentials to already be masked as secrets
// in any error messages.
exportCredentials({ AccessKeyId, SecretAccessKey, SessionToken });
exportCredentials({ AccessKeyId, SecretAccessKey, SessionToken }, outputCredentials, outputEnvCredentials);
} else if (!webIdentityTokenFile && !roleChaining) {
// Proceed only if credentials can be picked up
await credentialsClient.validateCredentials();
@@ -193,7 +195,7 @@ export async function run() {
);
} while (specialCharacterWorkaround && !verifyKeys(roleCredentials.Credentials));
core.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser?.AssumedRoleId}`);
exportCredentials(roleCredentials.Credentials, outputCredentials);
exportCredentials(roleCredentials.Credentials, outputCredentials, outputEnvCredentials);
// We need to validate the credentials in 2 of our use-cases
// First: self-hosted runners. If the GITHUB_ACTIONS environment variable
// is set to `true` then we are NOT in a self-hosted runner.
@@ -201,7 +203,9 @@ export async function run() {
if (!process.env.GITHUB_ACTIONS || AccessKeyId) {
await credentialsClient.validateCredentials(roleCredentials.Credentials?.AccessKeyId);
}
await exportAccountId(credentialsClient, maskAccountId);
if (outputEnvCredentials) {
await exportAccountId(credentialsClient, maskAccountId);
}
} else {
core.info('Proceeding with IAM user credentials');
}
+5
View File
@@ -45,4 +45,9 @@ describe('Configure AWS Credentials cleanup', {}, () => {
cleanup();
expect(core.setFailed).toHaveBeenCalled();
});
it(`doesn't export credentials as empty env variables if asked not to`, {}, () => {
vi.spyOn(core, 'getInput').mockImplementation(mocks.getInput(mocks.NO_ENV_CREDS_INPUTS));
cleanup();
expect(core.exportVariable).toHaveBeenCalledTimes(0);
})
});
+12 -1
View File
@@ -27,7 +27,7 @@ describe('Configure AWS Credentials helpers', {}, () => {
vi.spyOn(core, 'setOutput').mockImplementation(() => {});
vi.spyOn(core, 'setSecret').mockImplementation(() => {});
vi.spyOn(core, 'exportVariable').mockImplementation(() => {});
helpers.exportCredentials({ AccessKeyId: 'test', SecretAccessKey: 'test', SessionToken: 'test', Expiration: new Date(8640000000000000) }, true);
helpers.exportCredentials({ AccessKeyId: 'test', SecretAccessKey: 'test', SessionToken: 'test', Expiration: new Date(8640000000000000) }, true, true);
expect(core.setOutput).toHaveBeenCalledTimes(4);
expect(core.setSecret).toHaveBeenCalledTimes(3);
expect(core.exportVariable).toHaveBeenCalledTimes(3);
@@ -42,4 +42,15 @@ describe('Configure AWS Credentials helpers', {}, () => {
expect(process.env.AWS_DEFAULT_REGION).toBeUndefined;
process.env = env;
});
it(`won't output credentials to env if told not to`, {}, () => {
vi.spyOn(core, 'setOutput').mockImplementation(() => {});
vi.spyOn(core, 'setSecret').mockImplementation(() => {});
vi.spyOn(core, 'exportVariable').mockImplementation(() => {});
helpers.exportCredentials({ AccessKeyId: 'test', SecretAccessKey: 'test', SessionToken: 'test', Expiration: new Date(8640000000000000) }, true, false);
helpers.unsetCredentials(false);
helpers.exportRegion('fake-test-region', false);
expect(core.setOutput).toHaveBeenCalledTimes(4);
expect(core.setSecret).toHaveBeenCalledTimes(3);
expect(core.exportVariable).toHaveBeenCalledTimes(0);
});
});
+21
View File
@@ -312,5 +312,26 @@ describe('Configure AWS Credentials', {}, () => {
await run();
expect(core.setFailed).not.toHaveBeenCalled();
})
it('doesn\'t export credentials as environment variables if told not to', {}, async () => {
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
vi.spyOn(core, 'getInput').mockImplementation(mocks.getInput(mocks.NO_ENV_CREDS_INPUTS));
vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken');
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';
await run();
expect(core.setSecret).toHaveBeenCalledTimes(3);
expect(core.exportVariable).toHaveBeenCalledTimes(0);
expect(core.setFailed).not.toHaveBeenCalled();
})
it('can export creds as step outputs without exporting as env variables', {}, async () => {
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
vi.spyOn(core, 'getInput').mockImplementation(mocks.getInput(mocks.STEP_BUT_NO_ENV_INPUTS));
vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken');
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';
await run();
expect(core.setSecret).toHaveBeenCalledTimes(3);
expect(core.exportVariable).toHaveBeenCalledTimes(0);
expect(core.setOutput).toHaveBeenCalledTimes(4);
expect(core.setFailed).not.toHaveBeenCalled();
})
});
});
+11
View File
@@ -31,6 +31,17 @@ const inputs = {
'aws-region': 'fake-region-1',
'use-existing-credentials': 'true',
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
},
NO_ENV_CREDS_INPUTS: {
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
'aws-region': 'fake-region-1',
'output-env-credentials': 'false'
},
STEP_BUT_NO_ENV_INPUTS: {
'role-to-assume': 'arn:aws:iam::111111111111:role/MY-ROLE',
'aws-region': 'fake-region-1',
'output-env-credentials': 'false',
'output-credentials': 'true',
}
};