mirror of
https://github.com/aws-actions/configure-aws-credentials.git
synced 2026-09-04 06:15:07 +09:00
fix: mask both source and role credentials (#40)
This commit is contained in:
@@ -16,17 +16,15 @@ async function assumeRole(params) {
|
|||||||
const isDefined = i => !!i;
|
const isDefined = i => !!i;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
sourceAccountId,
|
||||||
roleToAssume,
|
roleToAssume,
|
||||||
roleExternalId,
|
roleExternalId,
|
||||||
roleDurationSeconds,
|
roleDurationSeconds,
|
||||||
roleSessionName,
|
roleSessionName,
|
||||||
accessKeyId,
|
|
||||||
secretAccessKey,
|
|
||||||
sessionToken,
|
|
||||||
region,
|
region,
|
||||||
} = params;
|
} = params;
|
||||||
assert(
|
assert(
|
||||||
[roleToAssume, roleDurationSeconds, roleSessionName, accessKeyId, secretAccessKey, region].every(isDefined),
|
[sourceAccountId, roleToAssume, roleDurationSeconds, roleSessionName, region].every(isDefined),
|
||||||
"Missing required input when assuming a Role."
|
"Missing required input when assuming a Role."
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -36,18 +34,12 @@ async function assumeRole(params) {
|
|||||||
'Missing required environment value. Are you running in GitHub Actions?'
|
'Missing required environment value. Are you running in GitHub Actions?'
|
||||||
);
|
);
|
||||||
|
|
||||||
const endpoint = util.format('https://sts.%s.amazonaws.com', region);
|
const sts = getStsClient(region);
|
||||||
|
|
||||||
const sts = new aws.STS({
|
|
||||||
accessKeyId, secretAccessKey, sessionToken, region, endpoint, customUserAgent: USER_AGENT
|
|
||||||
});
|
|
||||||
|
|
||||||
let roleArn = roleToAssume;
|
let roleArn = roleToAssume;
|
||||||
if (!roleArn.startsWith('arn:aws')) {
|
if (!roleArn.startsWith('arn:aws')) {
|
||||||
const identity = await sts.getCallerIdentity().promise();
|
|
||||||
const accountId = identity.Account;
|
|
||||||
// Supports only 'aws' partition. Customers in other partitions ('aws-cn') will need to provide full ARN
|
// Supports only 'aws' partition. Customers in other partitions ('aws-cn') will need to provide full ARN
|
||||||
roleArn = `arn:aws:iam::${accountId}:role/${roleArn}`;
|
roleArn = `arn:aws:iam::${sourceAccountId}:role/${roleArn}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const assumeRoleRequest = {
|
const assumeRoleRequest = {
|
||||||
@@ -125,15 +117,25 @@ function exportRegion(region) {
|
|||||||
core.exportVariable('AWS_REGION', region);
|
core.exportVariable('AWS_REGION', region);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportAccountId(maskAccountId) {
|
async function exportAccountId(maskAccountId, region) {
|
||||||
// Get the AWS account ID
|
// Get the AWS account ID
|
||||||
const sts = new aws.STS({customUserAgent: USER_AGENT});
|
const sts = getStsClient(region);
|
||||||
const identity = await sts.getCallerIdentity().promise();
|
const identity = await sts.getCallerIdentity().promise();
|
||||||
const accountId = identity.Account;
|
const accountId = identity.Account;
|
||||||
core.setOutput('aws-account-id', accountId);
|
core.setOutput('aws-account-id', accountId);
|
||||||
if (!maskAccountId || maskAccountId.toLowerCase() == 'true') {
|
if (!maskAccountId || maskAccountId.toLowerCase() == 'true') {
|
||||||
core.setSecret(accountId);
|
core.setSecret(accountId);
|
||||||
}
|
}
|
||||||
|
return accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStsClient(region) {
|
||||||
|
const endpoint = util.format('https://sts.%s.amazonaws.com', region);
|
||||||
|
return new aws.STS({
|
||||||
|
region,
|
||||||
|
endpoint,
|
||||||
|
customUserAgent: USER_AGENT
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
@@ -149,19 +151,28 @@ async function run() {
|
|||||||
const roleDurationSeconds = core.getInput('role-duration-seconds', {required: false}) || MAX_ACTION_RUNTIME;
|
const roleDurationSeconds = core.getInput('role-duration-seconds', {required: false}) || MAX_ACTION_RUNTIME;
|
||||||
const roleSessionName = core.getInput('role-session-name', { required: false }) || ROLE_SESSION_NAME;
|
const roleSessionName = core.getInput('role-session-name', { required: false }) || ROLE_SESSION_NAME;
|
||||||
|
|
||||||
|
// Always export the source credentials and account ID.
|
||||||
|
// The STS client for calling AssumeRole pulls creds from the environment.
|
||||||
|
// Plus, in the assume role case, if the AssumeRole call fails, we want
|
||||||
|
// the source credentials and accound ID to already be masked as secrets
|
||||||
|
// in any error messages.
|
||||||
|
exportRegion(region);
|
||||||
|
exportCredentials({accessKeyId, secretAccessKey, sessionToken});
|
||||||
|
const sourceAccountId = await exportAccountId(maskAccountId, region);
|
||||||
|
|
||||||
// Get role credentials if configured to do so
|
// Get role credentials if configured to do so
|
||||||
if (roleToAssume) {
|
if (roleToAssume) {
|
||||||
const roleCredentials = await assumeRole(
|
const roleCredentials = await assumeRole({
|
||||||
{accessKeyId, secretAccessKey, sessionToken, region, roleToAssume, roleExternalId, roleDurationSeconds, roleSessionName}
|
sourceAccountId,
|
||||||
);
|
region,
|
||||||
|
roleToAssume,
|
||||||
|
roleExternalId,
|
||||||
|
roleDurationSeconds,
|
||||||
|
roleSessionName
|
||||||
|
});
|
||||||
exportCredentials(roleCredentials);
|
exportCredentials(roleCredentials);
|
||||||
} else {
|
await exportAccountId(maskAccountId, region);
|
||||||
exportCredentials({accessKeyId, secretAccessKey, sessionToken});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
exportRegion(region);
|
|
||||||
|
|
||||||
await exportAccountId(maskAccountId);
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
core.setFailed(error.message);
|
core.setFailed(error.message);
|
||||||
|
|||||||
+40
-17
@@ -13,8 +13,9 @@ const FAKE_STS_SECRET_ACCESS_KEY = 'STS-AWS-SECRET-ACCESS-KEY';
|
|||||||
const FAKE_STS_SESSION_TOKEN = 'STS-AWS-SESSION-TOKEN';
|
const FAKE_STS_SESSION_TOKEN = 'STS-AWS-SESSION-TOKEN';
|
||||||
const FAKE_REGION = 'fake-region-1';
|
const FAKE_REGION = 'fake-region-1';
|
||||||
const FAKE_ACCOUNT_ID = '123456789012';
|
const FAKE_ACCOUNT_ID = '123456789012';
|
||||||
|
const FAKE_ROLE_ACCOUNT_ID = '111111111111';
|
||||||
const ROLE_NAME = 'MY-ROLE';
|
const ROLE_NAME = 'MY-ROLE';
|
||||||
const ROLE_ARN = 'arn:aws:iam::123456789012:role/MY-ROLE';
|
const ROLE_ARN = 'arn:aws:iam::111111111111:role/MY-ROLE';
|
||||||
const ENVIRONMENT_VARIABLE_OVERRIDES = {
|
const ENVIRONMENT_VARIABLE_OVERRIDES = {
|
||||||
SHOW_STACK_TRACE: 'true',
|
SHOW_STACK_TRACE: 'true',
|
||||||
GITHUB_REPOSITORY: 'MY-REPOSITORY-NAME',
|
GITHUB_REPOSITORY: 'MY-REPOSITORY-NAME',
|
||||||
@@ -68,12 +69,17 @@ describe('Configure AWS Credentials', () => {
|
|||||||
.fn()
|
.fn()
|
||||||
.mockImplementation(mockGetInput(DEFAULT_INPUTS));
|
.mockImplementation(mockGetInput(DEFAULT_INPUTS));
|
||||||
|
|
||||||
mockStsCallerIdentity.mockImplementation(() => {
|
mockStsCallerIdentity.mockReset();
|
||||||
return {
|
mockStsCallerIdentity
|
||||||
|
.mockReturnValueOnce({
|
||||||
promise() {
|
promise() {
|
||||||
return Promise.resolve({ Account: FAKE_ACCOUNT_ID });
|
return Promise.resolve({ Account: FAKE_ACCOUNT_ID });
|
||||||
}
|
}
|
||||||
};
|
})
|
||||||
|
.mockReturnValueOnce({
|
||||||
|
promise() {
|
||||||
|
return Promise.resolve({ Account: FAKE_ROLE_ACCOUNT_ID });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mockStsAssumeRole.mockImplementation(() => {
|
mockStsAssumeRole.mockImplementation(() => {
|
||||||
@@ -154,6 +160,7 @@ describe('Configure AWS Credentials', () => {
|
|||||||
test('error is caught by core.setFailed and caught', async () => {
|
test('error is caught by core.setFailed and caught', async () => {
|
||||||
process.env.SHOW_STACK_TRACE = 'false';
|
process.env.SHOW_STACK_TRACE = 'false';
|
||||||
|
|
||||||
|
mockStsCallerIdentity.mockReset();
|
||||||
mockStsCallerIdentity.mockImplementation(() => {
|
mockStsCallerIdentity.mockImplementation(() => {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
});
|
});
|
||||||
@@ -165,6 +172,7 @@ describe('Configure AWS Credentials', () => {
|
|||||||
|
|
||||||
test('error is caught by core.setFailed and passed', async () => {
|
test('error is caught by core.setFailed and passed', async () => {
|
||||||
|
|
||||||
|
mockStsCallerIdentity.mockReset();
|
||||||
mockStsCallerIdentity.mockImplementation(() => {
|
mockStsCallerIdentity.mockImplementation(() => {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
});
|
});
|
||||||
@@ -181,18 +189,33 @@ describe('Configure AWS Credentials', () => {
|
|||||||
|
|
||||||
await run();
|
await run();
|
||||||
expect(mockStsAssumeRole).toHaveBeenCalledTimes(1);
|
expect(mockStsAssumeRole).toHaveBeenCalledTimes(1);
|
||||||
expect(core.exportVariable).toHaveBeenCalledTimes(5);
|
expect(core.exportVariable).toHaveBeenCalledTimes(7);
|
||||||
expect(core.setSecret).toHaveBeenCalledTimes(4);
|
expect(core.setSecret).toHaveBeenCalledTimes(7);
|
||||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_ACCESS_KEY_ID', FAKE_STS_ACCESS_KEY_ID);
|
expect(core.setOutput).toHaveBeenCalledTimes(2);
|
||||||
expect(core.setSecret).toHaveBeenCalledWith(FAKE_STS_ACCESS_KEY_ID);
|
|
||||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SECRET_ACCESS_KEY', FAKE_STS_SECRET_ACCESS_KEY);
|
// first the source credentials are exported and masked
|
||||||
expect(core.setSecret).toHaveBeenCalledWith(FAKE_STS_SECRET_ACCESS_KEY);
|
expect(core.setSecret).toHaveBeenNthCalledWith(1, FAKE_ACCESS_KEY_ID);
|
||||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_SESSION_TOKEN', FAKE_STS_SESSION_TOKEN);
|
expect(core.setSecret).toHaveBeenNthCalledWith(2, FAKE_SECRET_ACCESS_KEY);
|
||||||
expect(core.setSecret).toHaveBeenCalledWith(FAKE_STS_SESSION_TOKEN);
|
expect(core.setSecret).toHaveBeenNthCalledWith(3, FAKE_ACCOUNT_ID);
|
||||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_DEFAULT_REGION', FAKE_REGION);
|
|
||||||
expect(core.exportVariable).toHaveBeenCalledWith('AWS_REGION', FAKE_REGION);
|
expect(core.exportVariable).toHaveBeenNthCalledWith(1, 'AWS_DEFAULT_REGION', FAKE_REGION);
|
||||||
expect(core.setOutput).toHaveBeenCalledWith('aws-account-id', FAKE_ACCOUNT_ID);
|
expect(core.exportVariable).toHaveBeenNthCalledWith(2, 'AWS_REGION', FAKE_REGION);
|
||||||
expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCOUNT_ID);
|
expect(core.exportVariable).toHaveBeenNthCalledWith(3, 'AWS_ACCESS_KEY_ID', FAKE_ACCESS_KEY_ID);
|
||||||
|
expect(core.exportVariable).toHaveBeenNthCalledWith(4, 'AWS_SECRET_ACCESS_KEY', FAKE_SECRET_ACCESS_KEY);
|
||||||
|
|
||||||
|
expect(core.setOutput).toHaveBeenNthCalledWith(1, 'aws-account-id', FAKE_ACCOUNT_ID);
|
||||||
|
|
||||||
|
// then the role credentials are exported and masked
|
||||||
|
expect(core.setSecret).toHaveBeenNthCalledWith(4, FAKE_STS_ACCESS_KEY_ID);
|
||||||
|
expect(core.setSecret).toHaveBeenNthCalledWith(5, FAKE_STS_SECRET_ACCESS_KEY);
|
||||||
|
expect(core.setSecret).toHaveBeenNthCalledWith(6, FAKE_STS_SESSION_TOKEN);
|
||||||
|
expect(core.setSecret).toHaveBeenNthCalledWith(7, FAKE_ROLE_ACCOUNT_ID);
|
||||||
|
|
||||||
|
expect(core.exportVariable).toHaveBeenNthCalledWith(5, 'AWS_ACCESS_KEY_ID', FAKE_STS_ACCESS_KEY_ID);
|
||||||
|
expect(core.exportVariable).toHaveBeenNthCalledWith(6, 'AWS_SECRET_ACCESS_KEY', FAKE_STS_SECRET_ACCESS_KEY);
|
||||||
|
expect(core.exportVariable).toHaveBeenNthCalledWith(7, 'AWS_SESSION_TOKEN', FAKE_STS_SESSION_TOKEN);
|
||||||
|
|
||||||
|
expect(core.setOutput).toHaveBeenNthCalledWith(2, 'aws-account-id', FAKE_ROLE_ACCOUNT_ID);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('role assumption tags', async () => {
|
test('role assumption tags', async () => {
|
||||||
@@ -268,7 +291,7 @@ describe('Configure AWS Credentials', () => {
|
|||||||
|
|
||||||
await run();
|
await run();
|
||||||
expect(mockStsAssumeRole).toHaveBeenCalledWith({
|
expect(mockStsAssumeRole).toHaveBeenCalledWith({
|
||||||
RoleArn: ROLE_ARN,
|
RoleArn: 'arn:aws:iam::123456789012:role/MY-ROLE',
|
||||||
RoleSessionName: 'GitHubActions',
|
RoleSessionName: 'GitHubActions',
|
||||||
DurationSeconds: 6 * 3600,
|
DurationSeconds: 6 * 3600,
|
||||||
Tags: [
|
Tags: [
|
||||||
|
|||||||
Reference in New Issue
Block a user