Skip to content
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

feat(custom-resources): NoEcho for sensitive data in provider framework #18097

Merged
merged 2 commits into from
Jan 3, 2022
Merged
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
1 change: 1 addition & 0 deletions packages/@aws-cdk/custom-resources/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ The return value from `onEvent` must be a JSON object with the following fields:
|-----|----|--------|-----------
|`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.
|`Data`|JSON|No|Resource attributes, which can later be retrieved through `Fn::GetAtt` on the custom resource object.
|`NoEcho`|Boolean|No|Whether to mask the output of the custom resource when retrieved by using the `Fn::GetAtt` function.
|*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.

[Custom Resource Provider Request]: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/crpg-ref-requests.html#crpg-ref-request-fields
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async function onEvent(cfnRequest: AWSLambda.CloudFormationCustomResourceEvent)
// determine if this is an async provider based on whether we have an isComplete handler defined.
// if it is not defined, then we are basically ready to return a positive response.
if (!process.env[consts.USER_IS_COMPLETE_FUNCTION_ARN_ENV]) {
return cfnResponse.submitResponse('SUCCESS', resourceEvent);
return cfnResponse.submitResponse('SUCCESS', resourceEvent, { noEcho: resourceEvent.NoEcho });
}

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

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

await cfnResponse.submitResponse('SUCCESS', response);
await cfnResponse.submitResponse('SUCCESS', response, { noEcho: event.NoEcho });
}

// invoked when completion retries are exhaused.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ interface OnEventResponse {
* Custom fields returned from OnEvent will be passed to IsComplete.
*/
readonly [key: string]: any;

/**
* Whether to mask the output of the custom resource when retrieved
* by using the `Fn::GetAtt` function. If set to `true`, all returned
* values are masked with asterisks (*****).
*
* @default false
*/
readonly NoEcho?: boolean;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,61 @@ test('if there is no user-defined "isComplete", the waiter will not be triggered
expectCloudFormationSuccess({ PhysicalResourceId: MOCK_PHYSICAL_ID });
});

describe('NoEcho', () => {
test('with onEvent', async () => {
// GIVEN
mocks.onEventImplMock = async () => ({
Data: {
Very: 'Sensitive',
},
NoEcho: true,
});

// WHEN
await simulateEvent({
RequestType: 'Create',
});

// THEN
expectCloudFormationSuccess({
Data: {
Very: 'Sensitive',
},
NoEcho: true,
});
});

test('with isComplete', async () => {
// GIVEN
mocks.onEventImplMock = async () => ({
Data: {
Very: 'Sensitive',
},
NoEcho: true,
});
mocks.isCompleteImplMock = async () => ({
Data: {
Also: 'Confidential',
},
IsComplete: true,
});

// WHEN
await simulateEvent({
RequestType: 'Create',
});

// THEN
expectCloudFormationSuccess({
Data: {
Very: 'Sensitive',
Also: 'Confidential',
},
NoEcho: true,
});
});
});

test('fails if user handler returns a non-object response', async () => {
// GIVEN
mocks.stringifyPayload = false;
Expand Down