Skip to content

Commit e138188

Browse files
authoredDec 13, 2021
fix(custom-resources): assumedRole from AwsCustomResource invocation leaked to next execution (#15776)
Fixes #15425 Credit to @nicolai-shape for proposing the fix itself. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 5497525 commit e138188

File tree

2 files changed

+161
-3
lines changed
  • packages/@aws-cdk/custom-resources

2 files changed

+161
-3
lines changed
 

‎packages/@aws-cdk/custom-resources/lib/aws-custom-resource/runtime/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -161,26 +161,26 @@ export async function handler(event: AWSLambda.CloudFormationCustomResourceEvent
161161

162162
if (call) {
163163

164+
let credentials;
164165
if (call.assumedRoleArn) {
165-
166166
const timestamp = (new Date()).getTime();
167167

168168
const params = {
169169
RoleArn: call.assumedRoleArn,
170170
RoleSessionName: `${timestamp}-${physicalResourceId}`.substring(0, 64),
171171
};
172172

173-
AWS.config.credentials = new AWS.ChainableTemporaryCredentials({
173+
credentials = new AWS.ChainableTemporaryCredentials({
174174
params: params,
175175
});
176-
177176
}
178177

179178
if (!Object.prototype.hasOwnProperty.call(AWS, call.service)) {
180179
throw Error(`Service ${call.service} does not exist in AWS SDK version ${AWS.VERSION}.`);
181180
}
182181
const awsService = new (AWS as any)[call.service]({
183182
apiVersion: call.apiVersion,
183+
credentials: credentials,
184184
region: call.region,
185185
});
186186

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import * as AWS from 'aws-sdk';
2+
import { PhysicalResourceId } from '../../../lib';
3+
import { handler } from '../../../lib/aws-custom-resource/runtime/index';
4+
5+
/* eslint-disable no-console */
6+
console.log = jest.fn();
7+
8+
jest.mock('aws-sdk', () => {
9+
return {
10+
...jest.requireActual('aws-sdk'),
11+
SSM: jest.fn(() => {
12+
return {
13+
config: {
14+
apiVersion: 'apiVersion',
15+
region: 'eu-west-1',
16+
},
17+
getParameter: () => {
18+
return {
19+
promise: async () => {},
20+
};
21+
},
22+
};
23+
}),
24+
};
25+
});
26+
27+
jest.mock('https', () => {
28+
return {
29+
request: (_: any, callback: () => void) => {
30+
return {
31+
on: () => undefined,
32+
write: () => true,
33+
end: callback,
34+
};
35+
},
36+
};
37+
});
38+
39+
afterEach(() => {
40+
jest.clearAllMocks();
41+
});
42+
43+
test('SDK global credentials are never set', async () => {
44+
// WHEN
45+
await handler({
46+
LogicalResourceId: 'logicalResourceId',
47+
RequestId: 'requestId',
48+
RequestType: 'Create',
49+
ResponseURL: 'responseUrl',
50+
ResourceProperties: {
51+
Create: JSON.stringify({
52+
action: 'getParameter',
53+
assumedRoleArn: 'arn:aws:iam::123456789012:role/CoolRole',
54+
parameters: {
55+
Name: 'foo',
56+
},
57+
physicalResourceId: PhysicalResourceId.of('id'),
58+
service: 'SSM',
59+
}),
60+
ServiceToken: 'serviceToken',
61+
},
62+
ResourceType: 'resourceType',
63+
ServiceToken: 'serviceToken',
64+
StackId: 'stackId',
65+
}, {} as AWSLambda.Context);
66+
67+
// THEN
68+
expect(AWS.config).toBeInstanceOf(AWS.Config);
69+
expect(AWS.config.credentials).toBeNull();
70+
});
71+
72+
test('SDK credentials are not persisted across subsequent invocations', async () => {
73+
// GIVEN
74+
const mockCreds = new AWS.ChainableTemporaryCredentials();
75+
jest.spyOn(AWS, 'ChainableTemporaryCredentials').mockReturnValue(mockCreds);
76+
77+
// WHEN
78+
await handler({
79+
LogicalResourceId: 'logicalResourceId',
80+
RequestId: 'requestId',
81+
RequestType: 'Create',
82+
ResponseURL: 'responseUrl',
83+
ResourceProperties: {
84+
Create: JSON.stringify({
85+
action: 'getParameter',
86+
parameters: {
87+
Name: 'foo',
88+
},
89+
physicalResourceId: PhysicalResourceId.of('id'),
90+
service: 'SSM',
91+
}),
92+
ServiceToken: 'serviceToken',
93+
},
94+
ResourceType: 'resourceType',
95+
ServiceToken: 'serviceToken',
96+
StackId: 'stackId',
97+
}, {} as AWSLambda.Context);
98+
99+
await handler({
100+
LogicalResourceId: 'logicalResourceId',
101+
RequestId: 'requestId',
102+
RequestType: 'Create',
103+
ResponseURL: 'responseUrl',
104+
ResourceProperties: {
105+
Create: JSON.stringify({
106+
action: 'getParameter',
107+
assumedRoleArn: 'arn:aws:iam::123456789012:role/CoolRole',
108+
parameters: {
109+
Name: 'foo',
110+
},
111+
physicalResourceId: PhysicalResourceId.of('id'),
112+
service: 'SSM',
113+
}),
114+
ServiceToken: 'serviceToken',
115+
},
116+
ResourceType: 'resourceType',
117+
ServiceToken: 'serviceToken',
118+
StackId: 'stackId',
119+
}, {} as AWSLambda.Context);
120+
121+
await handler({
122+
LogicalResourceId: 'logicalResourceId',
123+
RequestId: 'requestId',
124+
RequestType: 'Create',
125+
ResponseURL: 'responseUrl',
126+
ResourceProperties: {
127+
Create: JSON.stringify({
128+
action: 'getParameter',
129+
parameters: {
130+
Name: 'foo',
131+
},
132+
physicalResourceId: PhysicalResourceId.of('id'),
133+
service: 'SSM',
134+
}),
135+
ServiceToken: 'serviceToken',
136+
},
137+
ResourceType: 'resourceType',
138+
ServiceToken: 'serviceToken',
139+
StackId: 'stackId',
140+
}, {} as AWSLambda.Context);
141+
142+
// THEN
143+
expect(AWS.SSM).toHaveBeenNthCalledWith(1, {
144+
apiVersion: undefined,
145+
credentials: undefined,
146+
region: undefined,
147+
});
148+
expect(AWS.SSM).toHaveBeenNthCalledWith(2, {
149+
apiVersion: undefined,
150+
credentials: mockCreds,
151+
region: undefined,
152+
});
153+
expect(AWS.SSM).toHaveBeenNthCalledWith(3, {
154+
apiVersion: undefined,
155+
credentials: undefined,
156+
region: undefined,
157+
});
158+
});

0 commit comments

Comments
 (0)
Please sign in to comment.