Skip to content

Commit c3b3ce6

Browse files
authored
Merge pull request #1379 from aws-actions/disable-env-output
feat: Optional environment variable output
2 parents 151e7fe + 33adce1 commit c3b3ce6

File tree

11 files changed

+3145
-7024
lines changed

11 files changed

+3145
-7024
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,8 @@ See [action.yml](./action.yml) for more detail.
111111
| role-skip-session-tagging | Skips session tagging if set. | No |
112112
| inline-session-policy | You may further restrict the assumed role policy by defining an inline policy here. | No |
113113
| managed-session-policies | You may further restrict the assumed role policy by specifying a managed policy here. | No |
114-
| 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 |
114+
| 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 |
115+
| 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 |
115116
| unset-current-credentials | When set, attempts to unset any existing credentials in your action runner. | No |
116117
| disable-retry | Disabled retry/backoff logic for assume role calls. By default, retries are enabled. | No |
117118
| retry-max-attempts | Limits the number of retry attempts before giving up. Defaults to 12. | No |

action.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ inputs:
6161
output-credentials:
6262
description: Whether to set credentials as step output
6363
required: false
64+
output-env-credentials:
65+
description: Whether to export credentials as environment variables. If you set this to false, you probably want to use output-credentials.
66+
required: false
67+
default: true
6468
unset-current-credentials:
6569
description: Whether to unset the existing credentials in your runner. May be useful if you run this action multiple times in the same job
6670
required: false
@@ -84,3 +88,5 @@ outputs:
8488
description: The AWS secret access key for the provided credentials
8589
aws-session-token:
8690
description: The AWS session token for the provided credentials
91+
aws-expiration:
92+
description: The expiration time for the provided credentials

dist/cleanup/index.js

Lines changed: 1422 additions & 3393 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/index.js

Lines changed: 1607 additions & 3596 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/cleanup/index.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,21 @@ import { errorMessage } from '../helpers';
1313
*/
1414

1515
export function cleanup() {
16-
try {
17-
// The GitHub Actions toolkit does not have an option to completely unset
18-
// environment variables, so we overwrite the current value with an empty
19-
// string. The AWS CLI and AWS SDKs will behave correctly: they treat an
20-
// empty string value as if the environment variable does not exist.
21-
core.exportVariable('AWS_ACCESS_KEY_ID', '');
22-
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
23-
core.exportVariable('AWS_SESSION_TOKEN', '');
24-
core.exportVariable('AWS_DEFAULT_REGION', '');
25-
core.exportVariable('AWS_REGION', '');
26-
} catch (error) {
27-
core.setFailed(errorMessage(error));
16+
const outputEnvCredentialsInput = core.getInput('output-env-credentials', { required: false }) || 'true';
17+
if (outputEnvCredentialsInput === 'true') {
18+
try {
19+
// The GitHub Actions toolkit does not have an option to completely unset
20+
// environment variables, so we overwrite the current value with an empty
21+
// string. The AWS CLI and AWS SDKs will behave correctly: they treat an
22+
// empty string value as if the environment variable does not exist.
23+
core.exportVariable('AWS_ACCESS_KEY_ID', '');
24+
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
25+
core.exportVariable('AWS_SESSION_TOKEN', '');
26+
core.exportVariable('AWS_DEFAULT_REGION', '');
27+
core.exportVariable('AWS_REGION', '');
28+
} catch (error) {
29+
core.setFailed(errorMessage(error));
30+
}
2831
}
2932
}
3033
/* c8 ignore start */

src/helpers.ts

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,23 +39,38 @@ export function translateEnvVariables() {
3939

4040
// Configure the AWS CLI and AWS SDKs using environment variables and set them as secrets.
4141
// Setting the credentials as secrets masks them in Github Actions logs
42-
export function exportCredentials(creds?: Partial<Credentials>, outputCredentials?: boolean) {
42+
export function exportCredentials(
43+
creds?: Partial<Credentials>,
44+
outputCredentials?: boolean,
45+
outputEnvCredentials?: boolean,
46+
) {
4347
if (creds?.AccessKeyId) {
4448
core.setSecret(creds.AccessKeyId);
45-
core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId);
4649
}
4750

4851
if (creds?.SecretAccessKey) {
4952
core.setSecret(creds.SecretAccessKey);
50-
core.exportVariable('AWS_SECRET_ACCESS_KEY', creds.SecretAccessKey);
5153
}
5254

5355
if (creds?.SessionToken) {
5456
core.setSecret(creds.SessionToken);
55-
core.exportVariable('AWS_SESSION_TOKEN', creds.SessionToken);
56-
} else if (process.env.AWS_SESSION_TOKEN) {
57-
// clear session token from previous credentials action
58-
core.exportVariable('AWS_SESSION_TOKEN', '');
57+
}
58+
59+
if (outputEnvCredentials) {
60+
if (creds?.AccessKeyId) {
61+
core.exportVariable('AWS_ACCESS_KEY_ID', creds.AccessKeyId);
62+
}
63+
64+
if (creds?.SecretAccessKey) {
65+
core.exportVariable('AWS_SECRET_ACCESS_KEY', creds.SecretAccessKey);
66+
}
67+
68+
if (creds?.SessionToken) {
69+
core.exportVariable('AWS_SESSION_TOKEN', creds.SessionToken);
70+
} else if (process.env.AWS_SESSION_TOKEN) {
71+
// clear session token from previous credentials action
72+
core.exportVariable('AWS_SESSION_TOKEN', '');
73+
}
5974
}
6075

6176
if (outputCredentials) {
@@ -74,17 +89,21 @@ export function exportCredentials(creds?: Partial<Credentials>, outputCredential
7489
}
7590
}
7691

77-
export function unsetCredentials() {
78-
core.exportVariable('AWS_ACCESS_KEY_ID', '');
79-
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
80-
core.exportVariable('AWS_SESSION_TOKEN', '');
81-
core.exportVariable('AWS_REGION', '');
82-
core.exportVariable('AWS_DEFAULT_REGION', '');
92+
export function unsetCredentials(outputEnvCredentials?: boolean) {
93+
if (outputEnvCredentials) {
94+
core.exportVariable('AWS_ACCESS_KEY_ID', '');
95+
core.exportVariable('AWS_SECRET_ACCESS_KEY', '');
96+
core.exportVariable('AWS_SESSION_TOKEN', '');
97+
core.exportVariable('AWS_REGION', '');
98+
core.exportVariable('AWS_DEFAULT_REGION', '');
99+
}
83100
}
84101

85-
export function exportRegion(region: string) {
86-
core.exportVariable('AWS_DEFAULT_REGION', region);
87-
core.exportVariable('AWS_REGION', region);
102+
export function exportRegion(region: string, outputEnvCredentials?: boolean) {
103+
if (outputEnvCredentials) {
104+
core.exportVariable('AWS_DEFAULT_REGION', region);
105+
core.exportVariable('AWS_REGION', region);
106+
}
88107
}
89108

90109
// Obtains account ID from STS Client and sets it as output

src/index.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ export async function run() {
5656
const roleChaining = roleChainingInput.toLowerCase() === 'true';
5757
const outputCredentialsInput = core.getInput('output-credentials', { required: false }) || 'false';
5858
const outputCredentials = outputCredentialsInput.toLowerCase() === 'true';
59+
const outputEnvCredentialsInput = core.getInput('output-env-credentials', { required: false }) || 'true';
60+
const outputEnvCredentials = outputEnvCredentialsInput.toLowerCase() === 'true';
5961
const unsetCurrentCredentialsInput = core.getInput('unset-current-credentials', { required: false }) || 'false';
6062
const unsetCurrentCredentials = unsetCurrentCredentialsInput.toLowerCase() === 'true';
6163
const disableRetryInput = core.getInput('disable-retry', { required: false }) || 'false';
@@ -108,13 +110,13 @@ export async function run() {
108110
};
109111

110112
if (unsetCurrentCredentials) {
111-
unsetCredentials();
113+
unsetCredentials(outputEnvCredentials);
112114
}
113115

114116
if (!region.match(REGION_REGEX)) {
115117
throw new Error(`Region is not valid: ${region}`);
116118
}
117-
exportRegion(region);
119+
exportRegion(region, outputEnvCredentials);
118120

119121
// Instantiate credentials client
120122
const credentialsClient = new CredentialsClient({ region, proxyServer });
@@ -153,7 +155,7 @@ export async function run() {
153155
// Plus, in the assume role case, if the AssumeRole call fails, we want
154156
// the source credentials to already be masked as secrets
155157
// in any error messages.
156-
exportCredentials({ AccessKeyId, SecretAccessKey, SessionToken });
158+
exportCredentials({ AccessKeyId, SecretAccessKey, SessionToken }, outputCredentials, outputEnvCredentials);
157159
} else if (!webIdentityTokenFile && !roleChaining) {
158160
// Proceed only if credentials can be picked up
159161
await credentialsClient.validateCredentials();
@@ -193,15 +195,17 @@ export async function run() {
193195
);
194196
} while (specialCharacterWorkaround && !verifyKeys(roleCredentials.Credentials));
195197
core.info(`Authenticated as assumedRoleId ${roleCredentials.AssumedRoleUser?.AssumedRoleId}`);
196-
exportCredentials(roleCredentials.Credentials, outputCredentials);
198+
exportCredentials(roleCredentials.Credentials, outputCredentials, outputEnvCredentials);
197199
// We need to validate the credentials in 2 of our use-cases
198200
// First: self-hosted runners. If the GITHUB_ACTIONS environment variable
199201
// is set to `true` then we are NOT in a self-hosted runner.
200202
// Second: Customer provided credentials manually (IAM User keys stored in GH Secrets)
201203
if (!process.env.GITHUB_ACTIONS || AccessKeyId) {
202204
await credentialsClient.validateCredentials(roleCredentials.Credentials?.AccessKeyId);
203205
}
204-
await exportAccountId(credentialsClient, maskAccountId);
206+
if (outputEnvCredentials) {
207+
await exportAccountId(credentialsClient, maskAccountId);
208+
}
205209
} else {
206210
core.info('Proceeding with IAM user credentials');
207211
}

test/cleanup.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,9 @@ describe('Configure AWS Credentials cleanup', {}, () => {
4545
cleanup();
4646
expect(core.setFailed).toHaveBeenCalled();
4747
});
48+
it(`doesn't export credentials as empty env variables if asked not to`, {}, () => {
49+
vi.spyOn(core, 'getInput').mockImplementation(mocks.getInput(mocks.NO_ENV_CREDS_INPUTS));
50+
cleanup();
51+
expect(core.exportVariable).toHaveBeenCalledTimes(0);
52+
})
4853
});

test/helpers.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('Configure AWS Credentials helpers', {}, () => {
2727
vi.spyOn(core, 'setOutput').mockImplementation(() => {});
2828
vi.spyOn(core, 'setSecret').mockImplementation(() => {});
2929
vi.spyOn(core, 'exportVariable').mockImplementation(() => {});
30-
helpers.exportCredentials({ AccessKeyId: 'test', SecretAccessKey: 'test', SessionToken: 'test', Expiration: new Date(8640000000000000) }, true);
30+
helpers.exportCredentials({ AccessKeyId: 'test', SecretAccessKey: 'test', SessionToken: 'test', Expiration: new Date(8640000000000000) }, true, true);
3131
expect(core.setOutput).toHaveBeenCalledTimes(4);
3232
expect(core.setSecret).toHaveBeenCalledTimes(3);
3333
expect(core.exportVariable).toHaveBeenCalledTimes(3);
@@ -42,4 +42,15 @@ describe('Configure AWS Credentials helpers', {}, () => {
4242
expect(process.env.AWS_DEFAULT_REGION).toBeUndefined;
4343
process.env = env;
4444
});
45+
it(`won't output credentials to env if told not to`, {}, () => {
46+
vi.spyOn(core, 'setOutput').mockImplementation(() => {});
47+
vi.spyOn(core, 'setSecret').mockImplementation(() => {});
48+
vi.spyOn(core, 'exportVariable').mockImplementation(() => {});
49+
helpers.exportCredentials({ AccessKeyId: 'test', SecretAccessKey: 'test', SessionToken: 'test', Expiration: new Date(8640000000000000) }, true, false);
50+
helpers.unsetCredentials(false);
51+
helpers.exportRegion('fake-test-region', false);
52+
expect(core.setOutput).toHaveBeenCalledTimes(4);
53+
expect(core.setSecret).toHaveBeenCalledTimes(3);
54+
expect(core.exportVariable).toHaveBeenCalledTimes(0);
55+
});
4556
});

test/index.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,5 +312,26 @@ describe('Configure AWS Credentials', {}, () => {
312312
await run();
313313
expect(core.setFailed).not.toHaveBeenCalled();
314314
})
315+
it('doesn\'t export credentials as environment variables if told not to', {}, async () => {
316+
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
317+
vi.spyOn(core, 'getInput').mockImplementation(mocks.getInput(mocks.NO_ENV_CREDS_INPUTS));
318+
vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken');
319+
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';
320+
await run();
321+
expect(core.setSecret).toHaveBeenCalledTimes(3);
322+
expect(core.exportVariable).toHaveBeenCalledTimes(0);
323+
expect(core.setFailed).not.toHaveBeenCalled();
324+
})
325+
it('can export creds as step outputs without exporting as env variables', {}, async () => {
326+
mockedSTSClient.on(AssumeRoleWithWebIdentityCommand).resolvesOnce(mocks.outputs.STS_CREDENTIALS);
327+
vi.spyOn(core, 'getInput').mockImplementation(mocks.getInput(mocks.STEP_BUT_NO_ENV_INPUTS));
328+
vi.spyOn(core, 'getIDToken').mockResolvedValue('testoidctoken');
329+
process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN = 'fake-token';
330+
await run();
331+
expect(core.setSecret).toHaveBeenCalledTimes(3);
332+
expect(core.exportVariable).toHaveBeenCalledTimes(0);
333+
expect(core.setOutput).toHaveBeenCalledTimes(4);
334+
expect(core.setFailed).not.toHaveBeenCalled();
335+
})
315336
});
316337
});

0 commit comments

Comments
 (0)