Skip to content

Commit 6b4f92f

Browse files
authored
fix: custom resources log sensitive ResponseURL field (#20899)
All custom resource implementations start by logging the input `event`, so that issues will be easier to diagnose. Part of the payload of this event is the `ResponseURL` field, which is a pre-signed S3 URL where CloudFormation expects the success/failure result of the Custom Resource. By logging the `ResponseURL` to CloudWatch, we open an attack vector where an attacker who has access to read CloudWatch URLs can race the Custom Resource implementation to write a fake response to the presigned S3 URL, thereby faking the result of the CR (making the deployment fail when it should have succeeded, or make it succeed when it should have failed). Remove the `ResponseURL` from all logging output, and have the Custom Resource Provider remove it from the Payload that gets sent to the user function as well. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent e502590 commit 6b4f92f

File tree

98 files changed

+339
-586
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

98 files changed

+339
-586
lines changed

packages/@aws-cdk/aws-chatbot/test/chatbot-logretention.integ.snapshot/asset.22bb41d703c8e7a9a1712308f455fcf58cc012b0a386c9df563a6244a61e6665/index.js

+5-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/aws-dynamodb/lib/replica-handler/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { IsCompleteRequest, IsCompleteResponse, OnEventRequest, OnEventResp
33
import { DynamoDB } from 'aws-sdk'; // eslint-disable-line import/no-extraneous-dependencies
44

55
export async function onEventHandler(event: OnEventRequest): Promise<OnEventResponse> {
6-
console.log('Event: %j', event);
6+
console.log('Event: %j', { ...event, ResponseURL: '...' });
77

88
const dynamodb = new DynamoDB();
99

@@ -50,7 +50,7 @@ export async function onEventHandler(event: OnEventRequest): Promise<OnEventResp
5050
}
5151

5252
export async function isCompleteHandler(event: IsCompleteRequest): Promise<IsCompleteResponse> {
53-
console.log('Event: %j', event);
53+
console.log('Event: %j', { ...event, ResponseURL: '...' });
5454

5555
const dynamodb = new DynamoDB();
5656

packages/@aws-cdk/aws-dynamodb/test/global-replicas-provisioned.integ.snapshot/asset.5d88959fad6bed204d22b24bf15826b8c7591c586a60a313e54f1948d9cdf80f/index.js

+5-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/aws-dynamodb/test/global.integ.snapshot/asset.5d88959fad6bed204d22b24bf15826b8c7591c586a60a313e54f1948d9cdf80f/index.js

+5-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/@aws-cdk/aws-ecs-patterns/test/ec2/multiple-application-load-balanced-ecs-service.integ.snapshot/aws-ecs-integ.assets.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
2-
"version": "17.0.0",
2+
"version": "20.0.0",
33
"files": {
4-
"9abf2316979a565d3a86e43cce5b25037aee1b5cd3885835f8b3daec825b517e": {
4+
"2cc27380df0d00a7348926c8fe9b6ae056ac18cee09087d824792e9611cde97e": {
55
"source": {
66
"path": "aws-ecs-integ.template.json",
77
"packaging": "file"
88
},
99
"destinations": {
1010
"current_account-current_region": {
1111
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12-
"objectKey": "9abf2316979a565d3a86e43cce5b25037aee1b5cd3885835f8b3daec825b517e.json",
12+
"objectKey": "2cc27380df0d00a7348926c8fe9b6ae056ac18cee09087d824792e9611cde97e.json",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}

packages/@aws-cdk/aws-ecs-patterns/test/ec2/multiple-application-load-balanced-ecs-service.integ.snapshot/aws-ecs-integ.template.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -729,7 +729,7 @@
729729
"Type": "AWS::Lambda::Function",
730730
"Properties": {
731731
"Code": {
732-
"ZipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(event))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(event))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n task_arns = container_instance_task_arns(cluster, instance_arn)\n \n if task_arns:\n print('Instance ARN %s has task ARNs %s' % (instance_arn, ', '.join(task_arns)))\n\n while has_tasks(cluster, instance_arn, task_arns):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\ndef container_instance_task_arns(cluster, instance_arn):\n \"\"\"Fetch tasks for a container instance ARN.\"\"\"\n arns = ecs.list_tasks(cluster=cluster, containerInstance=instance_arn)['taskArns']\n return arns\n\ndef has_tasks(cluster, instance_arn, task_arns):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n task_count = None\n\n if task_arns:\n # Fetch details for tasks running on the container instance\n tasks = ecs.describe_tasks(cluster=cluster, tasks=task_arns)['tasks']\n if tasks:\n # Consider any non-stopped tasks as running\n task_count = sum(task['lastStatus'] != 'STOPPED' for task in tasks) + instance['pendingTasksCount']\n \n if not task_count:\n # Fallback to instance task counts if detailed task information is unavailable\n task_count = instance['runningTasksCount'] + instance['pendingTasksCount']\n \n print('Instance %s has %s tasks' % (instance_arn, task_count))\n\n return task_count > 0\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n"
732+
"ZipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(dict(event, ResponseURL='...')))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(dict(event, ResponseURL='...')))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n task_arns = container_instance_task_arns(cluster, instance_arn)\n\n if task_arns:\n print('Instance ARN %s has task ARNs %s' % (instance_arn, ', '.join(task_arns)))\n\n while has_tasks(cluster, instance_arn, task_arns):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\ndef container_instance_task_arns(cluster, instance_arn):\n \"\"\"Fetch tasks for a container instance ARN.\"\"\"\n arns = ecs.list_tasks(cluster=cluster, containerInstance=instance_arn)['taskArns']\n return arns\n\ndef has_tasks(cluster, instance_arn, task_arns):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n task_count = None\n\n if task_arns:\n # Fetch details for tasks running on the container instance\n tasks = ecs.describe_tasks(cluster=cluster, tasks=task_arns)['tasks']\n if tasks:\n # Consider any non-stopped tasks as running\n task_count = sum(task['lastStatus'] != 'STOPPED' for task in tasks) + instance['pendingTasksCount']\n\n if not task_count:\n # Fallback to instance task counts if detailed task information is unavailable\n task_count = instance['runningTasksCount'] + instance['pendingTasksCount']\n\n print('Instance %s has %s tasks' % (instance_arn, task_count))\n\n return task_count > 0\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n"
733733
},
734734
"Role": {
735735
"Fn::GetAtt": [

packages/@aws-cdk/aws-ecs-patterns/test/ec2/multiple-application-load-balanced-ecs-service.integ.snapshot/manifest.json

+6
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,12 @@
321321
"data": "myServiceTaskDefTaskRole1C1DE6CC"
322322
}
323323
],
324+
"/aws-ecs-integ/myService/TaskDef/TaskRole/DefaultPolicy/Resource": [
325+
{
326+
"type": "aws:cdk:logicalId",
327+
"data": "myServiceTaskDefTaskRoleDefaultPolicyD48473C0"
328+
}
329+
],
324330
"/aws-ecs-integ/myService/TaskDef/Resource": [
325331
{
326332
"type": "aws:cdk:logicalId",

packages/@aws-cdk/aws-ecs-patterns/test/ec2/multiple-application-load-balanced-ecs-service.integ.snapshot/tree.json

+52-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"path": "Tree",
1010
"constructInfo": {
1111
"fqn": "constructs.Construct",
12-
"version": "10.0.9"
12+
"version": "10.1.33"
1313
}
1414
},
1515
"aws-ecs-integ": {
@@ -1143,7 +1143,7 @@
11431143
"aws:cdk:cloudformation:type": "AWS::Lambda::Function",
11441144
"aws:cdk:cloudformation:props": {
11451145
"code": {
1146-
"zipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(event))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(event))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n task_arns = container_instance_task_arns(cluster, instance_arn)\n \n if task_arns:\n print('Instance ARN %s has task ARNs %s' % (instance_arn, ', '.join(task_arns)))\n\n while has_tasks(cluster, instance_arn, task_arns):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\ndef container_instance_task_arns(cluster, instance_arn):\n \"\"\"Fetch tasks for a container instance ARN.\"\"\"\n arns = ecs.list_tasks(cluster=cluster, containerInstance=instance_arn)['taskArns']\n return arns\n\ndef has_tasks(cluster, instance_arn, task_arns):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n task_count = None\n\n if task_arns:\n # Fetch details for tasks running on the container instance\n tasks = ecs.describe_tasks(cluster=cluster, tasks=task_arns)['tasks']\n if tasks:\n # Consider any non-stopped tasks as running\n task_count = sum(task['lastStatus'] != 'STOPPED' for task in tasks) + instance['pendingTasksCount']\n \n if not task_count:\n # Fallback to instance task counts if detailed task information is unavailable\n task_count = instance['runningTasksCount'] + instance['pendingTasksCount']\n \n print('Instance %s has %s tasks' % (instance_arn, task_count))\n\n return task_count > 0\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n"
1146+
"zipFile": "import boto3, json, os, time\n\necs = boto3.client('ecs')\nautoscaling = boto3.client('autoscaling')\n\n\ndef lambda_handler(event, context):\n print(json.dumps(dict(event, ResponseURL='...')))\n cluster = os.environ['CLUSTER']\n snsTopicArn = event['Records'][0]['Sns']['TopicArn']\n lifecycle_event = json.loads(event['Records'][0]['Sns']['Message'])\n instance_id = lifecycle_event.get('EC2InstanceId')\n if not instance_id:\n print('Got event without EC2InstanceId: %s', json.dumps(dict(event, ResponseURL='...')))\n return\n\n instance_arn = container_instance_arn(cluster, instance_id)\n print('Instance %s has container instance ARN %s' % (lifecycle_event['EC2InstanceId'], instance_arn))\n\n if not instance_arn:\n return\n\n task_arns = container_instance_task_arns(cluster, instance_arn)\n\n if task_arns:\n print('Instance ARN %s has task ARNs %s' % (instance_arn, ', '.join(task_arns)))\n\n while has_tasks(cluster, instance_arn, task_arns):\n time.sleep(10)\n\n try:\n print('Terminating instance %s' % instance_id)\n autoscaling.complete_lifecycle_action(\n LifecycleActionResult='CONTINUE',\n **pick(lifecycle_event, 'LifecycleHookName', 'LifecycleActionToken', 'AutoScalingGroupName'))\n except Exception as e:\n # Lifecycle action may have already completed.\n print(str(e))\n\n\ndef container_instance_arn(cluster, instance_id):\n \"\"\"Turn an instance ID into a container instance ARN.\"\"\"\n arns = ecs.list_container_instances(cluster=cluster, filter='ec2InstanceId==' + instance_id)['containerInstanceArns']\n if not arns:\n return None\n return arns[0]\n\ndef container_instance_task_arns(cluster, instance_arn):\n \"\"\"Fetch tasks for a container instance ARN.\"\"\"\n arns = ecs.list_tasks(cluster=cluster, containerInstance=instance_arn)['taskArns']\n return arns\n\ndef has_tasks(cluster, instance_arn, task_arns):\n \"\"\"Return True if the instance is running tasks for the given cluster.\"\"\"\n instances = ecs.describe_container_instances(cluster=cluster, containerInstances=[instance_arn])['containerInstances']\n if not instances:\n return False\n instance = instances[0]\n\n if instance['status'] == 'ACTIVE':\n # Start draining, then try again later\n set_container_instance_to_draining(cluster, instance_arn)\n return True\n\n task_count = None\n\n if task_arns:\n # Fetch details for tasks running on the container instance\n tasks = ecs.describe_tasks(cluster=cluster, tasks=task_arns)['tasks']\n if tasks:\n # Consider any non-stopped tasks as running\n task_count = sum(task['lastStatus'] != 'STOPPED' for task in tasks) + instance['pendingTasksCount']\n\n if not task_count:\n # Fallback to instance task counts if detailed task information is unavailable\n task_count = instance['runningTasksCount'] + instance['pendingTasksCount']\n\n print('Instance %s has %s tasks' % (instance_arn, task_count))\n\n return task_count > 0\n\ndef set_container_instance_to_draining(cluster, instance_arn):\n ecs.update_container_instances_state(\n cluster=cluster,\n containerInstances=[instance_arn], status='DRAINING')\n\n\ndef pick(dct, *keys):\n \"\"\"Pick a subset of a dict.\"\"\"\n return {k: v for k, v in dct.items() if k in keys}\n"
11471147
},
11481148
"role": {
11491149
"Fn::GetAtt": [
@@ -1240,7 +1240,7 @@
12401240
},
12411241
"constructInfo": {
12421242
"fqn": "constructs.Construct",
1243-
"version": "10.0.9"
1243+
"version": "10.1.33"
12441244
}
12451245
},
12461246
"LifecycleHookDrainHook": {
@@ -1735,6 +1735,54 @@
17351735
"fqn": "@aws-cdk/aws-iam.CfnRole",
17361736
"version": "0.0.0"
17371737
}
1738+
},
1739+
"DefaultPolicy": {
1740+
"id": "DefaultPolicy",
1741+
"path": "aws-ecs-integ/myService/TaskDef/TaskRole/DefaultPolicy",
1742+
"children": {
1743+
"Resource": {
1744+
"id": "Resource",
1745+
"path": "aws-ecs-integ/myService/TaskDef/TaskRole/DefaultPolicy/Resource",
1746+
"attributes": {
1747+
"aws:cdk:cloudformation:type": "AWS::IAM::Policy",
1748+
"aws:cdk:cloudformation:props": {
1749+
"policyDocument": {
1750+
"Statement": [
1751+
{
1752+
"Action": [
1753+
"logs:CreateLogStream",
1754+
"logs:DescribeLogGroups",
1755+
"logs:DescribeLogStreams",
1756+
"logs:PutLogEvents",
1757+
"ssmmessages:CreateControlChannel",
1758+
"ssmmessages:CreateDataChannel",
1759+
"ssmmessages:OpenControlChannel",
1760+
"ssmmessages:OpenDataChannel"
1761+
],
1762+
"Effect": "Allow",
1763+
"Resource": "*"
1764+
}
1765+
],
1766+
"Version": "2012-10-17"
1767+
},
1768+
"policyName": "myServiceTaskDefTaskRoleDefaultPolicyD48473C0",
1769+
"roles": [
1770+
{
1771+
"Ref": "myServiceTaskDefTaskRole1C1DE6CC"
1772+
}
1773+
]
1774+
}
1775+
},
1776+
"constructInfo": {
1777+
"fqn": "@aws-cdk/aws-iam.CfnPolicy",
1778+
"version": "0.0.0"
1779+
}
1780+
}
1781+
},
1782+
"constructInfo": {
1783+
"fqn": "@aws-cdk/aws-iam.Policy",
1784+
"version": "0.0.0"
1785+
}
17381786
}
17391787
},
17401788
"constructInfo": {
@@ -1942,6 +1990,7 @@
19421990
"minimumHealthyPercent": 50
19431991
},
19441992
"enableEcsManagedTags": false,
1993+
"enableExecuteCommand": true,
19451994
"healthCheckGracePeriodSeconds": 60,
19461995
"launchType": "EC2",
19471996
"loadBalancers": [

packages/@aws-cdk/aws-ecs-patterns/test/ec2/scheduled-ecs-task.lit.integ.snapshot/aws-ecs-integ-ecs.assets.json

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
{
2-
"version": "17.0.0",
2+
"version": "20.0.0",
33
"files": {
4-
"62462a0ca4f5e1fde18ddd2e2ad6537c111d2bd84b82653a8628047c552bca14": {
4+
"ef02919701446f0e1c908b12e2e437448544c6ac7df2f65225cb0cd189188fd1": {
55
"source": {
66
"path": "aws-ecs-integ-ecs.template.json",
77
"packaging": "file"
88
},
99
"destinations": {
1010
"current_account-current_region": {
1111
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
12-
"objectKey": "62462a0ca4f5e1fde18ddd2e2ad6537c111d2bd84b82653a8628047c552bca14.json",
12+
"objectKey": "ef02919701446f0e1c908b12e2e437448544c6ac7df2f65225cb0cd189188fd1.json",
1313
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
1414
}
1515
}

0 commit comments

Comments
 (0)