Skip to content

Commit

Permalink
feat(custom-resources): NoEcho for sensitive data in provider framewo…
Browse files Browse the repository at this point in the history
…rk (#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*
  • Loading branch information
jogold committed Jan 3, 2022
1 parent 9b81662 commit 621a410
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/@aws-cdk/custom-resources/README.md
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
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
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
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

0 comments on commit 621a410

Please sign in to comment.