Skip to content

Commit 621a410

Browse files
authored
feat(custom-resources): NoEcho for sensitive data in provider framework (#18097)
The `noEcho` option was available in `submitResponse()` but not exposed. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 9b81662 commit 621a410

File tree

4 files changed

+68
-3
lines changed

4 files changed

+68
-3
lines changed

packages/@aws-cdk/custom-resources/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ The return value from `onEvent` must be a JSON object with the following fields:
143143
|-----|----|--------|-----------
144144
|`PhysicalResourceId`|String|No|The allocated/assigned physical ID of the resource. If omitted for `Create` events, the event's `RequestId` will be used. For `Update`, the current physical ID will be used. If a different value is returned, CloudFormation will follow with a subsequent `Delete` for the previous ID (resource replacement). For `Delete`, it will always return the current physical resource ID, and if the user returns a different one, an error will occur.
145145
|`Data`|JSON|No|Resource attributes, which can later be retrieved through `Fn::GetAtt` on the custom resource object.
146+
|`NoEcho`|Boolean|No|Whether to mask the output of the custom resource when retrieved by using the `Fn::GetAtt` function.
146147
|*any*|*any*|No|Any other field included in the response will be passed through to `isComplete`. This can sometimes be useful to pass state between the handlers.
147148

148149
[Custom Resource Provider Request]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html#crpg-ref-request-fields

packages/@aws-cdk/custom-resources/lib/provider-framework/runtime/framework.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ async function onEvent(cfnRequest: AWSLambda.CloudFormationCustomResourceEvent)
3939
// determine if this is an async provider based on whether we have an isComplete handler defined.
4040
// if it is not defined, then we are basically ready to return a positive response.
4141
if (!process.env[consts.USER_IS_COMPLETE_FUNCTION_ARN_ENV]) {
42-
return cfnResponse.submitResponse('SUCCESS', resourceEvent);
42+
return cfnResponse.submitResponse('SUCCESS', resourceEvent, { noEcho: resourceEvent.NoEcho });
4343
}
4444

4545
// ok, we are not complete, so kick off the waiter workflow
@@ -62,7 +62,7 @@ async function isComplete(event: AWSCDKAsyncCustomResource.IsCompleteRequest) {
6262
const isCompleteResult = await invokeUserFunction(consts.USER_IS_COMPLETE_FUNCTION_ARN_ENV, event) as IsCompleteResponse;
6363
log('user isComplete returned:', isCompleteResult);
6464

65-
// if we are not complete, reeturn false, and don't send a response back.
65+
// if we are not complete, return false, and don't send a response back.
6666
if (!isCompleteResult.IsComplete) {
6767
if (isCompleteResult.Data && Object.keys(isCompleteResult.Data).length > 0) {
6868
throw new Error('"Data" is not allowed if "IsComplete" is "False"');
@@ -79,7 +79,7 @@ async function isComplete(event: AWSCDKAsyncCustomResource.IsCompleteRequest) {
7979
},
8080
};
8181

82-
await cfnResponse.submitResponse('SUCCESS', response);
82+
await cfnResponse.submitResponse('SUCCESS', response, { noEcho: event.NoEcho });
8383
}
8484

8585
// invoked when completion retries are exhaused.

packages/@aws-cdk/custom-resources/lib/provider-framework/types.d.ts

+9
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ interface OnEventResponse {
8080
* Custom fields returned from OnEvent will be passed to IsComplete.
8181
*/
8282
readonly [key: string]: any;
83+
84+
/**
85+
* Whether to mask the output of the custom resource when retrieved
86+
* by using the `Fn::GetAtt` function. If set to `true`, all returned
87+
* values are masked with asterisks (*****).
88+
*
89+
* @default false
90+
*/
91+
readonly NoEcho?: boolean;
8392
}
8493

8594
/**

packages/@aws-cdk/custom-resources/test/provider-framework/runtime.test.ts

+55
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,61 @@ test('if there is no user-defined "isComplete", the waiter will not be triggered
230230
expectCloudFormationSuccess({ PhysicalResourceId: MOCK_PHYSICAL_ID });
231231
});
232232

233+
describe('NoEcho', () => {
234+
test('with onEvent', async () => {
235+
// GIVEN
236+
mocks.onEventImplMock = async () => ({
237+
Data: {
238+
Very: 'Sensitive',
239+
},
240+
NoEcho: true,
241+
});
242+
243+
// WHEN
244+
await simulateEvent({
245+
RequestType: 'Create',
246+
});
247+
248+
// THEN
249+
expectCloudFormationSuccess({
250+
Data: {
251+
Very: 'Sensitive',
252+
},
253+
NoEcho: true,
254+
});
255+
});
256+
257+
test('with isComplete', async () => {
258+
// GIVEN
259+
mocks.onEventImplMock = async () => ({
260+
Data: {
261+
Very: 'Sensitive',
262+
},
263+
NoEcho: true,
264+
});
265+
mocks.isCompleteImplMock = async () => ({
266+
Data: {
267+
Also: 'Confidential',
268+
},
269+
IsComplete: true,
270+
});
271+
272+
// WHEN
273+
await simulateEvent({
274+
RequestType: 'Create',
275+
});
276+
277+
// THEN
278+
expectCloudFormationSuccess({
279+
Data: {
280+
Very: 'Sensitive',
281+
Also: 'Confidential',
282+
},
283+
NoEcho: true,
284+
});
285+
});
286+
});
287+
233288
test('fails if user handler returns a non-object response', async () => {
234289
// GIVEN
235290
mocks.stringifyPayload = false;

0 commit comments

Comments
 (0)