Skip to content

feat: Add ARN output and masking option in exportAccountId function #1191

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ inputs:
mask-aws-account-id:
description: Whether to mask the AWS account ID for these credentials as a secret value. By default the account ID will not be masked
required: false
mask-arn:
description: Whether to mask the Amazon Resource Name (ARN) for these credentials as a secret value. By default the Amazon Resource Name (ARN) will not be masked
required: false
role-duration-seconds:
description: Role duration in seconds. Default is one hour.
required: false
Expand Down
16 changes: 15 additions & 1 deletion dist/cleanup/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/cleanup/src/helpers.d.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 20 additions & 4 deletions dist/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,33 @@ export function exportRegion(region: string) {
}

// Obtains account ID from STS Client and sets it as output
export async function exportAccountId(credentialsClient: CredentialsClient, maskAccountId?: boolean) {
export async function exportAccountId(
credentialsClient: CredentialsClient,
maskAccountId?: boolean,
maskArn?: boolean
) {
const client = credentialsClient.stsClient;
const identity = await client.send(new GetCallerIdentityCommand({}));
const accountId = identity.Account;
const arn = identity.Arn;
if (!accountId) {
throw new Error('Could not get Account ID from STS. Did you set credentials?');
}
if (maskAccountId) {
core.setSecret(accountId);
} else {
core.info(`Authenticated as accountId ${accountId}`);
}
if (!arn) {
throw new Error('Could not get Amazon Resource Name (ARN) from STS. Did you set credentials?');
}
if (maskArn) {
core.setSecret(arn);
} else {
core.info(`Authenticated as arn ${arn}`);
}
core.setOutput('aws-account-id', accountId);
core.setOutput('arn', arn);
return accountId;
}

Expand Down
8 changes: 5 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export async function run() {
const audience = core.getInput('audience', { required: false });
const maskAccountIdInput = core.getInput('mask-aws-account-id', { required: false }) || 'false';
const maskAccountId = maskAccountIdInput.toLowerCase() === 'true';
const maskArnInput = core.getInput('mask-arn', { required: false }) || 'false';
const maskArn = maskArnInput.toLowerCase() === 'true';
const roleExternalId = core.getInput('role-external-id', { required: false });
const webIdentityTokenFile = core.getInput('web-identity-token-file', { required: false });
const roleDuration = parseInt(core.getInput('role-duration-seconds', { required: false })) || DEFAULT_ROLE_DURATION;
Expand Down Expand Up @@ -131,15 +133,15 @@ export async function run() {
} else if (!webIdentityTokenFile && !roleChaining) {
// Proceed only if credentials can be picked up
await credentialsClient.validateCredentials();
sourceAccountId = await exportAccountId(credentialsClient, maskAccountId);
sourceAccountId = await exportAccountId(credentialsClient, maskAccountId, maskArn);
}

if (AccessKeyId || roleChaining) {
// Validate that the SDK can actually pick up credentials.
// This validates cases where this action is using existing environment credentials,
// and cases where the user intended to provide input credentials but the secrets inputs resolved to empty strings.
await credentialsClient.validateCredentials(AccessKeyId, roleChaining);
sourceAccountId = await exportAccountId(credentialsClient, maskAccountId);
sourceAccountId = await exportAccountId(credentialsClient, maskAccountId, maskArn);
}

// Get role credentials if configured to do so
Expand Down Expand Up @@ -177,7 +179,7 @@ export async function run() {
if (!process.env['GITHUB_ACTIONS'] || AccessKeyId) {
await credentialsClient.validateCredentials(roleCredentials.Credentials?.AccessKeyId);
}
await exportAccountId(credentialsClient, maskAccountId);
await exportAccountId(credentialsClient, maskAccountId, maskArn);
} else {
core.info('Proceeding with IAM user credentials');
}
Expand Down
40 changes: 38 additions & 2 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const FAKE_STS_SESSION_TOKEN = 'STSAWSSESSIONTOKEN';
const FAKE_ASSUMED_ROLE_ID = 'AROAFAKEASSUMEDROLEID';
const FAKE_REGION = 'fake-region-1';
const FAKE_ACCOUNT_ID = '123456789012';
const FAKE_USER_ARN = 'arn:aws:iam:123456789012:user/FAKE-USER';
const FAKE_ROLE_ACCOUNT_ID = '111111111111';
const ROLE_NAME = 'MY-ROLE';
const ROLE_ARN = 'arn:aws:iam::111111111111:role/MY-ROLE';
Expand Down Expand Up @@ -103,7 +104,10 @@ describe('Configure AWS Credentials', () => {
});
mockedSTS
.on(GetCallerIdentityCommand)
.resolvesOnce({ Account: FAKE_ACCOUNT_ID })
.resolvesOnce({
Account: FAKE_ACCOUNT_ID,
Arn: FAKE_USER_ARN,
})
.resolvesOnce({ Account: FAKE_ROLE_ACCOUNT_ID });
mockedSTS.on(AssumeRoleCommand).resolves({
Credentials: {
Expand Down Expand Up @@ -156,6 +160,9 @@ describe('Configure AWS Credentials', () => {
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', FAKE_REGION);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', FAKE_REGION);
expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID);
expect(core.info).toHaveBeenCalledWith(`Authenticated as accountId ${FAKE_ACCOUNT_ID}`);
expect(core.setOutput).toHaveBeenCalledWith('arn', FAKE_USER_ARN);
expect(core.info).toHaveBeenCalledWith(`Authenticated as arn ${FAKE_USER_ARN}`);
});

test('action fails when github env vars are not set', async () => {
Expand Down Expand Up @@ -234,6 +241,9 @@ describe('Configure AWS Credentials', () => {
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'eu-west-1');
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'eu-west-1');
expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID);
expect(core.info).toHaveBeenCalledWith(`Authenticated as accountId ${FAKE_ACCOUNT_ID}`);
expect(core.setOutput).toHaveBeenCalledWith('arn', FAKE_USER_ARN);
expect(core.info).toHaveBeenCalledWith(`Authenticated as arn ${FAKE_USER_ARN}`);
});

test('existing env var creds are cleared', async () => {
Expand All @@ -256,6 +266,9 @@ describe('Configure AWS Credentials', () => {
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'eu-west-1');
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'eu-west-1');
expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID);
expect(core.info).toHaveBeenCalledWith(`Authenticated as accountId ${FAKE_ACCOUNT_ID}`);
expect(core.setOutput).toHaveBeenCalledWith('arn', FAKE_USER_ARN);
expect(core.info).toHaveBeenCalledWith(`Authenticated as arn ${FAKE_USER_ARN}`);
});

test('validates region name', async () => {
Expand Down Expand Up @@ -295,10 +308,33 @@ describe('Configure AWS Credentials', () => {
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'us-east-1');
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'us-east-1');
expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID);
expect(core.setOutput).toHaveBeenCalledWith('arn', FAKE_USER_ARN);
expect(core.info).toHaveBeenCalledWith(`Authenticated as arn ${FAKE_USER_ARN}`);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCOUNT_ID);
expect(core.setSecret).toHaveBeenCalledTimes(3);
});

test('can opt into masking ARN', async () => {
const mockInputs = { ...CREDS_INPUTS, 'aws-region': 'us-east-1', 'mask-arn': 'true' };
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(mockInputs));

await run();

expect(mockedSTS.commandCalls(AssumeRoleCommand)).toHaveLength(0);
expect(core.exportVariable).toHaveBeenCalledTimes(4);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCESS_KEY_ID);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_SECRET_ACCESS_KEY);
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', 'us-east-1');
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', 'us-east-1');
expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID);
expect(core.info).toHaveBeenCalledWith(`Authenticated as accountId ${FAKE_ACCOUNT_ID}`);
expect(core.setOutput).toHaveBeenCalledWith('arn', FAKE_USER_ARN);
expect(core.setSecret).toHaveBeenCalledWith(FAKE_USER_ARN);
expect(core.setSecret).toHaveBeenCalledTimes(3);
});

test('error is caught by core.setFailed and caught', async () => {
jest.spyOn(core, 'getInput').mockImplementation(mockGetInput(DEFAULT_INPUTS));
mockedSTS.reset();
Expand Down Expand Up @@ -851,6 +887,6 @@ describe('Configure AWS Credentials', () => {

await run();

expect(core.setOutput).toHaveBeenCalledTimes(4);
expect(core.setOutput).toHaveBeenCalledTimes(5);
});
});