Skip to content

Commit 73c7552

Browse files
committed
Try validating credentials
1 parent 9f7f57b commit 73c7552

File tree

3 files changed

+176
-1
lines changed

3 files changed

+176
-1
lines changed

dist/index.js

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

index.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,50 @@ async function exportAccountId(maskAccountId, region) {
132132
return accountId;
133133
}
134134

135+
function loadCredentials() {
136+
// Force the SDK to re-resolve credentials with the default provider chain.
137+
//
138+
// This action typically sets credentials in the environment via environment variables.
139+
// The SDK never refreshes those env-var-based credentials after initial load.
140+
// In case there were already env-var creds set in the actions environment when this action
141+
// loaded, this action needs to refresh the SDK creds after overwriting those environment variables.
142+
//
143+
// The credentials object needs to be entirely recreated (instead of simply refreshed),
144+
// because the credential object type could change when this action writes env var creds.
145+
// For example, the first load could return EC2 instance metadata credentials
146+
// in a self-hosted runner, and the second load could return environment credentials
147+
// from an assume-role call in this action.
148+
aws.config.credentials = null;
149+
150+
return new Promise((resolve, reject) => {
151+
aws.config.getCredentials((err) => {
152+
if (err) {
153+
reject(err);
154+
}
155+
resolve(aws.config.credentials);
156+
})
157+
});
158+
}
159+
160+
async function validateCredentials(expectedAccessKeyId) {
161+
let credentials;
162+
try {
163+
credentials = await loadCredentials();
164+
165+
if (!credentials.accessKeyId) {
166+
throw new Error('Access key ID empty after loading credentials');
167+
}
168+
} catch (error) {
169+
throw new Error(`Credentials could not be loaded, please check your action inputs: ${error.message}`);
170+
}
171+
172+
const actualAccessKeyId = credentials.accessKeyId;
173+
174+
if (expectedAccessKeyId && expectedAccessKeyId != actualAccessKeyId) {
175+
throw new Error('Unexpected failure: Credentials loaded by the SDK do not match the access key ID configured by the action');
176+
}
177+
}
178+
135179
function getStsClient(region) {
136180
return new aws.STS({
137181
region,
@@ -172,6 +216,13 @@ async function run() {
172216
exportCredentials({accessKeyId, secretAccessKey, sessionToken});
173217
}
174218

219+
// Regardless of whether any source credentials were provided as inputs,
220+
// validate that the SDK can actually pick up credentials. This validates
221+
// cases where this action is on a self-hosted runner that doesn't have credentials
222+
// configured correctly, and cases where the user intended to provide input
223+
// credentials but the secrets inputs resolved to empty strings.
224+
await validateCredentials(accessKeyId);
225+
175226
const sourceAccountId = await exportAccountId(maskAccountId, region);
176227

177228
// Get role credentials if configured to do so
@@ -185,6 +236,7 @@ async function run() {
185236
roleSessionName
186237
});
187238
exportCredentials(roleCredentials);
239+
await validateCredentials(roleCredentials.accessKeyId);
188240
await exportAccountId(maskAccountId, region);
189241
}
190242
}

index.test.js

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
const core = require('@actions/core');
22
const assert = require('assert');
3-
3+
const aws = require('aws-sdk');
44
const run = require('.');
55

66
jest.mock('@actions/core');
@@ -49,6 +49,9 @@ const mockStsAssumeRole = jest.fn();
4949

5050
jest.mock('aws-sdk', () => {
5151
return {
52+
config: {
53+
getCredentials: jest.fn()
54+
},
5255
STS: jest.fn(() => ({
5356
getCallerIdentity: mockStsCallerIdentity,
5457
assumeRole: mockStsAssumeRole,
@@ -82,6 +85,21 @@ describe('Configure AWS Credentials', () => {
8285
}
8386
});
8487

88+
aws.config.getCredentials.mockReset();
89+
aws.config.getCredentials
90+
.mockImplementationOnce(callback => {
91+
aws.config.credentials = {
92+
accessKeyId: FAKE_ACCESS_KEY_ID
93+
}
94+
callback(null);
95+
})
96+
.mockImplementationOnce(callback => {
97+
aws.config.credentials = {
98+
accessKeyId: FAKE_STS_ACCESS_KEY_ID
99+
}
100+
callback(null);
101+
});
102+
85103
mockStsAssumeRole.mockImplementation(() => {
86104
return {
87105
promise() {
@@ -134,6 +152,59 @@ describe('Configure AWS Credentials', () => {
134152
expect(core.setSecret).toHaveBeenCalledWith(FAKE_ACCOUNT_ID);
135153
});
136154

155+
test('action with no accessible credentials fails', async () => {
156+
process.env.SHOW_STACK_TRACE = 'false';
157+
const mockInputs = {'aws-region': FAKE_REGION};
158+
core.getInput = jest
159+
.fn()
160+
.mockImplementation(mockGetInput(mockInputs));
161+
aws.config.getCredentials.mockReset();
162+
aws.config.getCredentials.mockImplementation(callback => {
163+
callback(new Error('No credentials to load'));
164+
});
165+
166+
await run();
167+
168+
expect(core.setFailed).toHaveBeenCalledWith("Credentials could not be loaded, please check your action inputs: No credentials to load");
169+
});
170+
171+
test('action with empty credentials fails', async () => {
172+
process.env.SHOW_STACK_TRACE = 'false';
173+
const mockInputs = {'aws-region': FAKE_REGION};
174+
core.getInput = jest
175+
.fn()
176+
.mockImplementation(mockGetInput(mockInputs));
177+
aws.config.getCredentials.mockReset();
178+
aws.config.getCredentials.mockImplementation(callback => {
179+
aws.config.credentials = {
180+
accessKeyId: ''
181+
}
182+
callback(null);
183+
});
184+
185+
await run();
186+
187+
expect(core.setFailed).toHaveBeenCalledWith("Credentials could not be loaded, please check your action inputs: Access key ID empty after loading credentials");
188+
});
189+
190+
test('action fails when credentials are not set in the SDK correctly', async () => {
191+
process.env.SHOW_STACK_TRACE = 'false';
192+
core.getInput = jest
193+
.fn()
194+
.mockImplementation(mockGetInput(ASSUME_ROLE_INPUTS));
195+
aws.config.getCredentials.mockReset();
196+
aws.config.getCredentials.mockImplementation(callback => {
197+
aws.config.credentials = {
198+
accessKeyId: FAKE_ACCESS_KEY_ID
199+
}
200+
callback(null);
201+
});
202+
203+
await run();
204+
205+
expect(core.setFailed).toHaveBeenCalledWith("Unexpected failure: Credentials loaded by the SDK do not match the access key ID configured by the action");
206+
});
207+
137208
test('session token is optional', async () => {
138209
const mockInputs = {...CREDS_INPUTS, 'aws-region': 'eu-west-1'};
139210
core.getInput = jest

0 commit comments

Comments
 (0)