AWSTemplateFormatVersion: "2010-09-09" Description: Legacy infrastructure Parameters: LatestAmiId: Type: "AWS::SSM::Parameter::Value" Default: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" CoreNetworkId: Type: String Default: "" CreateCWANPeering: Type: String Description: Create the Cloud WAN peering to TGW Default: "false" AllowedValues: [true, false] CreateCWANAttachment: Type: String Description: Create the TGW Route Table attachment Default: "false" AllowedValues: [true, false] CreateVPCRoutes: Type: String Description: Create the VPC routes to Transit Gateway Default: "false" AllowedValues: [true, false] Conditions: CreatePeering: !And - !Equals [!Ref CreateCWANPeering, 'true'] - !Not [!Equals [!Ref CoreNetworkId, ""]] CreateAttachment: !And - !Equals [!Ref CreateCWANPeering, 'true'] - !Equals [!Ref CreateCWANAttachment, 'true'] - !Not [!Equals [!Ref CoreNetworkId, ""]] CreateRoutes: !Equals [!Ref CreateVPCRoutes, 'true'] Mappings: RegionMap: us-west-2: legacy: 10.2.0.0/16 tgwasn: 64515 eu-north-1: legacy: 10.12.0.0/16 tgwasn: 64516 Resources: # ---------- LEGACY VPC ---------- # VPC resource VPCLegacy: Metadata: cfn_nag: rules_to_suppress: - id: W60 reason: VPC Flow Logs not required for workshop Type: AWS::EC2::VPC Properties: CidrBlock: !FindInMap - RegionMap - !Ref "AWS::Region" - legacy EnableDnsSupport: true EnableDnsHostnames: true Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "vpc" # Subnets VPCLegacySubnetWorkload1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPCLegacy CidrBlock: !Select [0, !Cidr [!GetAtt VPCLegacy.CidrBlock, 6, 8]] AvailabilityZone: !Select - 0 - !GetAZs Ref: "AWS::Region" MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "workload-subnet-1" VPCLegacySubnetWorkload2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPCLegacy CidrBlock: !Select [1, !Cidr [!GetAtt VPCLegacy.CidrBlock, 6, 8]] AvailabilityZone: !Select - 1 - !GetAZs Ref: "AWS::Region" MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "workload-subnet-2" VPCLegacySubnetEndpoints1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPCLegacy CidrBlock: !Select [2, !Cidr [!GetAtt VPCLegacy.CidrBlock, 6, 8]] AvailabilityZone: !Select - 0 - !GetAZs Ref: "AWS::Region" MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "endpoints-subnet-1" VPCLegacySubnetEndpoints2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPCLegacy CidrBlock: !Select [3, !Cidr [!GetAtt VPCLegacy.CidrBlock, 6, 8]] AvailabilityZone: !Select - 1 - !GetAZs Ref: "AWS::Region" MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "endpoints-subnet-2" VPCLegacySubnetTGW1: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPCLegacy CidrBlock: !Select [4, !Cidr [!GetAtt VPCLegacy.CidrBlock, 6, 8]] AvailabilityZone: !Select - 0 - !GetAZs Ref: "AWS::Region" MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "tgw-subnet-1" VPCLegacySubnetTGW2: Type: AWS::EC2::Subnet Properties: VpcId: !Ref VPCLegacy CidrBlock: !Select [5, !Cidr [!GetAtt VPCLegacy.CidrBlock, 6, 8]] AvailabilityZone: !Select - 1 - !GetAZs Ref: "AWS::Region" MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "tgw-subnet-2" # Route Tables VPCLegacyRouteTableWorkload1: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPCLegacy Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "workload-rt-1" VPCLegacyWorkloadSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref VPCLegacyRouteTableWorkload1 SubnetId: !Ref VPCLegacySubnetWorkload1 VPCLegacyRouteTableWorkload2: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPCLegacy Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "workload-rt-2" VPCLegacyWorkloadSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref VPCLegacyRouteTableWorkload2 SubnetId: !Ref VPCLegacySubnetWorkload2 VPCLegacyRouteTableEndpoints1: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPCLegacy Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "endpoints-rt-1" VPCLegacyEndpointsSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref VPCLegacyRouteTableEndpoints1 SubnetId: !Ref VPCLegacySubnetEndpoints1 VPCLegacyRouteTableEndpoints2: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPCLegacy Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "endpoints-rt-2" VPCLegacyEndpointsSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref VPCLegacyRouteTableEndpoints2 SubnetId: !Ref VPCLegacySubnetEndpoints2 VPCLegacyRouteTableTGW1: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPCLegacy Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "tgw-rt-1" VPCLegacyTGWSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref VPCLegacyRouteTableTGW1 SubnetId: !Ref VPCLegacySubnetTGW1 VPCLegacyRouteTableTGW2: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref VPCLegacy Tags: - Key: Name Value: !Join - "-" - - "legacy" - !Ref AWS::Region - "tgw-rt-2" VPCLegacyTGWSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref VPCLegacyRouteTableTGW2 SubnetId: !Ref VPCLegacySubnetTGW2 # Transit Gateway Attachment VPCLegacyTGWAttachment: Type: AWS::EC2::TransitGatewayAttachment Properties: TransitGatewayId: !Ref TransitGateway VpcId: !Ref VPCLegacy SubnetIds: - !Ref VPCLegacySubnetTGW1 - !Ref VPCLegacySubnetTGW2 Tags: - Key: Name Value: !Join - "-" - - "tgw" - "attachment" - !Ref AWS::Region # Route from the Workload subnets to the TGW Attachment VPCLegacyRouteWorkloadSubnet1: Condition: CreateRoutes Type: AWS::EC2::Route DependsOn: VPCLegacyTGWAttachment Properties: DestinationCidrBlock: 0.0.0.0/0 RouteTableId: !Ref VPCLegacyRouteTableWorkload1 TransitGatewayId: !Ref TransitGateway VPCLegacyRouteWorkloadSubnet2: Condition: CreateRoutes Type: AWS::EC2::Route DependsOn: VPCLegacyTGWAttachment Properties: DestinationCidrBlock: 0.0.0.0/0 RouteTableId: !Ref VPCLegacyRouteTableWorkload2 TransitGatewayId: !Ref TransitGateway # Security Groups (Instance and VPC endpoint) VPCLegacyInstanceSecurityGroup: Metadata: cfn_nag: rules_to_suppress: - id: W9 reason: CIDR is constrained to CloudWAN demo range Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Legacy VPC - Instance Security Group VpcId: !Ref VPCLegacy SecurityGroupIngress: - Description: Allowing ICMP traffic IpProtocol: icmp FromPort: "-1" ToPort: "-1" CidrIp: 10.0.0.0/8 VPCLegacyEndpointsSecurityGroup: Metadata: cfn_nag: rules_to_suppress: - id: W9 reason: CIDR is constrained to CloudWAN demo range Type: AWS::EC2::SecurityGroup Properties: GroupDescription: Non-Production VPC - VPC Endpoints Security Group VpcId: !Ref VPCLegacy SecurityGroupIngress: - Description: Allowing HTTPS IpProtocol: tcp FromPort: 443 ToPort: 443 SourceSecurityGroupId: !Ref VPCLegacyInstanceSecurityGroup # EC2 Instances EC2InstanceLegacyWorkload1: Type: AWS::EC2::Instance Properties: InstanceType: t3.micro SecurityGroupIds: - Ref: VPCLegacyInstanceSecurityGroup SubnetId: Ref: VPCLegacySubnetWorkload1 ImageId: !Ref LatestAmiId IamInstanceProfile: !Ref EC2SSMInstanceProfileLegacy Tags: - Key: Name Value: "Legacy-Instance-1" EC2InstanceLegacyWorkload2: Type: AWS::EC2::Instance Properties: InstanceType: t3.micro SecurityGroupIds: - Ref: VPCLegacyInstanceSecurityGroup SubnetId: Ref: VPCLegacySubnetWorkload2 ImageId: !Ref LatestAmiId IamInstanceProfile: !Ref EC2SSMInstanceProfileLegacy Tags: - Key: Name Value: "Legacy-Instance-2" # SSM Endpoints SSMLegacyVPCEndpoint: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm VpcId: !Ref VPCLegacy SubnetIds: - !Ref VPCLegacySubnetEndpoints1 - !Ref VPCLegacySubnetEndpoints2 SecurityGroupIds: - !Ref VPCLegacyEndpointsSecurityGroup VpcEndpointType: Interface PrivateDnsEnabled: True SSMMessagesLegacyVPCEndpoint: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages VpcId: !Ref VPCLegacy SubnetIds: - !Ref VPCLegacySubnetEndpoints1 - !Ref VPCLegacySubnetEndpoints2 SecurityGroupIds: - !Ref VPCLegacyEndpointsSecurityGroup VpcEndpointType: Interface PrivateDnsEnabled: True EC2MessagesLegacyVPCEndpoint: Type: AWS::EC2::VPCEndpoint Properties: ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages VpcId: !Ref VPCLegacy SubnetIds: - !Ref VPCLegacySubnetEndpoints1 - !Ref VPCLegacySubnetEndpoints2 SecurityGroupIds: - !Ref VPCLegacyEndpointsSecurityGroup VpcEndpointType: Interface PrivateDnsEnabled: True # ---------- TRANSIT GATEWAY ---------- # Resource TransitGateway: Type: AWS::EC2::TransitGateway Properties: AmazonSideAsn: !FindInMap - RegionMap - !Ref "AWS::Region" - tgwasn DefaultRouteTableAssociation: disable DefaultRouteTablePropagation: disable Description: !Join - "-" - - "Transit Gateway" - !Ref AWS::Region Tags: - Key: Name Value: !Join - "-" - - "tgw" - !Ref AWS::Region # Route Table TransitGatewayRouteTable: Type: AWS::EC2::TransitGatewayRouteTable Properties: TransitGatewayId: !Ref TransitGateway Tags: - Key: Name Value: !Join - "-" - - "tgw" - "rt" - !Ref AWS::Region # Propagation and Association TransitGatewayRouteTableAssociation: Type: AWS::EC2::TransitGatewayRouteTableAssociation Properties: TransitGatewayAttachmentId: !Ref VPCLegacyTGWAttachment TransitGatewayRouteTableId: !Ref TransitGatewayRouteTable TransitGatewayRouteTablePropagation: Type: AWS::EC2::TransitGatewayRouteTablePropagation Properties: TransitGatewayAttachmentId: !Ref VPCLegacyTGWAttachment TransitGatewayRouteTableId: !Ref TransitGatewayRouteTable # ---------- IAM ROLE (EC2 INSTANCE) ---------- # EC2 Instance Role (SSM access) EC2SSMIAMRoleLegacy: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonSSMManagedInstanceCore Path: / EC2SSMInstanceProfileLegacy: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - !Ref EC2SSMIAMRoleLegacy # ---------- TRANSIT GATEWAY - CLOUD WAN PEERING ---------- # Cloud WAN - Transit Gateway peering TGWCWANPeering: Condition: CreatePeering Type: AWS::NetworkManager::TransitGatewayPeering Properties: CoreNetworkId: !Ref CoreNetworkId TransitGatewayArn: !Join - ":" - - "arn:aws:ec2" - !Ref AWS::Region - !Ref AWS::AccountId - !Join - "/" - - "transit-gateway" - !GetAtt TransitGateway.Id Tags: - Key: Name Value: !Join - "-" - - "peering" - !Ref AWS::Region # TGW Policy Table and peering association (Custom Resource) TGWPolicyTable: Condition: CreatePeering Type: Custom::TGWPolicyTable DependsOn: - TGWCWANPeering Properties: ServiceToken: !GetAtt TGWPolicyTableFunction.Arn TransitGatewayId: !GetAtt TransitGateway.Id TGWPeeringAttachmentId: !GetAtt TGWCWANPeering.TransitGatewayPeeringAttachmentId # Transit Gateway Route Table Attachment TGWRouteTableAttachment: Type: AWS::NetworkManager::TransitGatewayRouteTableAttachment DependsOn: - TGWPolicyTable Properties: PeeringId: !Ref TGWCWANPeering TransitGatewayRouteTableArn: !Join - ":" - - "arn:aws:ec2" - !Ref AWS::Region - !Ref AWS::AccountId - !Join - "/" - - "transit-gateway-route-table" - !Ref TransitGatewayRouteTable Tags: - Key: legacy Value: "true" CustomResourcesRole: Condition: CreatePeering Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Policies: - PolicyName: AllowTGWNetworkManager PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - "ec2:CreateTransitGatewayPolicyTable" - "ec2:AssociateTransitGatewayPolicyTable" - "ec2:DisassociateTransitGatewayPolicyTable" - "ec2:DeleteTransitGatewayPolicyTable" - "ec2:DescribeTransitGatewayPolicyTables" - "ec2:DescribeRegions" - "ec2:GetTransitGatewayPolicyTableAssociations" - "ec2:GetTransitGatewayPolicyTableEntries" - "ec2:CreateTags" Resource: - "*" ManagedPolicyArns: - !Sub arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole TGWPolicyTableFunctionLogGroup: Condition: CreatePeering Metadata: cfn_nag: rules_to_suppress: - id: W84 reason: Encryption not required for this log group Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${TGWPolicyTableFunction} RetentionInDays: 7 TGWPolicyTableFunction: Condition: CreatePeering Metadata: cfn_nag: rules_to_suppress: - id: W58 reason: CWL permissions granted by use of AWSLambdaBasicExecutionRole - id: W89 reason: No requirement for this function to be in a VPC - id: W92 reason: No requirement to limit simultaneous executions Type: AWS::Lambda::Function Properties: Description: Create TGW Policy Table and associate it to Cloud WAN peering Runtime: python3.9 Timeout: 900 Role: !GetAtt CustomResourcesRole.Arn Handler: index.lambda_handler Code: ZipFile: |- import sys import subprocess from pip._internal import main # pip3 install - update boto3 to latest version subprocess.call('pip3 install -I -q boto3 -t /tmp/ --no-cache-dir --disable-pip-version-check'.split(), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) sys.path.insert(1, '/tmp/') import boto3 import os import logging import json import time import cfnresponse from botocore.config import Config from botocore.exceptions import ClientError log = logging.getLogger("handler") log.setLevel(logging.INFO) def lambda_handler(event, context): try: log.info("Received event: %s", json.dumps(event)) log.info(boto3.__version__) # We obtain the parameters from the Custom Resource Event action = event["RequestType"] tgw_id = event["ResourceProperties"]["TransitGatewayId"] tgw_peering_attachment_id = event["ResourceProperties"]["TGWPeeringAttachmentId"] # We obtain the AWS Region region = os.environ['AWS_REGION'] # boto3 client configuration ec2 = boto3.client("ec2") response = {} # Actions to perform if we are creating the resources if action == "Create": log.info("Creating transit gateway policy table for transit gateway %s", tgw_id) response = create_tgw_policytable(ec2, tgw_id, tgw_peering_attachment_id, region) if action == "Delete": log.info("Deleting transit gateway policy table for transit gateway %s", tgw_id) response = delete_tgw_policytable(ec2, tgw_id, tgw_peering_attachment_id, region) if action == "Update": log.info("Updating transit gateway policy table for tgw %s", tgw_id) # Getting the previous values of the TGW and Core Network old_tgw_peering_attachment_id = event["OldResourceProperties"]["TGWPeeringAttachmentId"] # Update TGW Policy Table association response = update_tgw_peering_attachment(ec2, tgw_id, old_tgw_peering_attachment_id, tgw_peering_attachment_id, region) if "Return" in response: if response["Return"]: cfnresponse.send(event, context, cfnresponse.SUCCESS, {}) return cfnresponse.send( event, context, cfnresponse.FAILED, {}, reason="API request failed" ) return cfnresponse.send(event, context, cfnresponse.SUCCESS, response) log.info(response) except: log.exception("whoops") cfnresponse.send( event, context, cfnresponse.FAILED, {}, reason="Caught exception, check logs", ) def create_tgw_policytable(ec2, tgw_id, tgw_peering_attachment_id, region): try: # Initialization of the response response = {} # Create TGW policy table tgw_policy_table_id = ec2.create_transit_gateway_policy_table( TransitGatewayId=tgw_id, TagSpecifications=[ { 'ResourceType': 'transit-gateway-policy-table', 'Tags': [ { 'Key': 'Name', 'Value': f'tgw-policy-table-{region}' }, ] }, ] )['TransitGatewayPolicyTable']['TransitGatewayPolicyTableId'] # Adding TGW Policy Table ID in response response['TransitGatewayPolicyTableId'] = tgw_policy_table_id # We associate the Policy Table with the Peering (via the TGW Attachment ID) association_state = ec2.associate_transit_gateway_policy_table( TransitGatewayPolicyTableId=tgw_policy_table_id, TransitGatewayAttachmentId=tgw_peering_attachment_id )['Association']['State'] # Waiting for the association to be available while association_state == "associating": time.sleep(5) association_state = ec2.get_transit_gateway_policy_table_associations(TransitGatewayPolicyTableId=tgw_policy_table_id)['Associations'][0]['State'] log.info("EVERYTHING CREATED") return response except Exception as e: log.exception("whoops") cfnresponse.send( event, context, cfnresponse.FAILED, {}, reason="Caught exception, check logs", ) def delete_tgw_policytable(ec2, tgw_id, tgw_peering_attachment_id, region): try: # Initialization of the response response = {} # We get the Transit Gateway Policy Table ID tgw_policy_table_id = ec2.describe_transit_gateway_policy_tables( Filters=[ { 'Name': 'transit-gateway-id', 'Values': [tgw_id] }, { 'Name': 'state', 'Values': ['available'] }, { 'Name': 'tag-key', 'Values': ['Name'] }, { 'Name': 'tag-value', 'Values': [f'tgw-policy-table-{region}'] } ] )['TransitGatewayPolicyTables'][0]['TransitGatewayPolicyTableId'] # Adding the TGW Policy Table ID in the response response['TransitGatewayPolicyTableId'] = tgw_policy_table_id # We disassociate the Transit Gateway Policy Table from the Peering disassociation_state = ec2.disassociate_transit_gateway_policy_table( TransitGatewayPolicyTableId=tgw_policy_table_id, TransitGatewayAttachmentId=tgw_peering_attachment_id )['Association']['State'] # We wait for the disassociation to finish while disassociation_state == "disassociating": time.sleep(5) disassociation_state = ec2.describe_transit_gateway_policy_tables( TransitGatewayPolicyTableIds = [tgw_policy_table_id] )["TransitGatewayPolicyTables"][0]["State"] # We delete the Transit Gateway Policy Table delete_tgw_policy_table = ec2.delete_transit_gateway_policy_table(TransitGatewayPolicyTableId=tgw_policy_table_id) log.info("EVERYTHING DELETED") return response except Exception as e: log.exception("whoops") cfnresponse.send( event, context, cfnresponse.FAILED, {}, reason="Caught exception, check logs", ) def update_tgw_peering_attachment(ec2, tgw_id, old_tgw_peering_attachment_id, tgw_peering_attachment_id, region): try: # Initialization of the response response = {} # We get the Transit Gateway Policy Table ID tgw_policy_table_id = ec2.describe_transit_gateway_policy_tables( Filters=[ { 'Name': 'transit-gateway-id', 'Values': [tgw_id] }, { 'Name': 'state', 'Values': ['available'] }, { 'Name': 'tag-key', 'Values': ['Name'] }, { 'Name': 'tag-value', 'Values': [f'tgw-policy-table-{region}'] } ] )['TransitGatewayPolicyTables'][0]['TransitGatewayPolicyTableId'] # Adding the TGW Policy Table ID in the response response['TransitGatewayPolicyTableId'] = tgw_policy_table_id # We dissassociate the Policy Table from the old Transit Gateway Peering Attachment ID disassociation_state = ec2.disassociate_transit_gateway_policy_table( TransitGatewayPolicyTableId=tgw_policy_table_id, TransitGatewayAttachmentId=old_tgw_peering_attachment_id )['Association']['State'] # We wait for the disassociation to finish while disassociation_state == "disassociating": time.sleep(5) disassociation_state = ec2.describe_transit_gateway_policy_tables( TransitGatewayPolicyTableIds = [tgw_policy_table_id] )["TransitGatewayPolicyTables"][0]["State"] # We associate the Policy Table to the new Transit Gateway Peering Attachment ID association_state = ec2.associate_transit_gateway_policy_table( TransitGatewayPolicyTableId=tgw_policy_table_id, TransitGatewayAttachmentId=tgw_peering_attachment_id )['Association']['State'] # Waiting for the association to be available while association_state == "associating": time.sleep(5) association_state = ec2.get_transit_gateway_policy_table_associations(TransitGatewayPolicyTableId=tgw_policy_table_id)['Associations'][0]['State'] log.info("EVERYTHING CREATED") return response except Exception as e: log.exception("whoops") cfnresponse.send( event, context, cfnresponse.FAILED, {}, reason="Caught exception, check logs", )