AWSTemplateFormatVersion: '2010-09-09' Description: The template creates the Spoke VPC for the domain whitelisting using the AWS Transit Gateway and Squid. Parameters: CidrBlock: Default: '10.10.0.0/16' Description: The CIDR Block for the VPC Type: String SubnetCidrBlock: Default: '10.10.0.0/24' Description: The CIDR Block for the Subnet Type: String SubnetCidrBlock2: Default: '10.10.1.0/24' Description: The CIDR Block for the Subnet Type: String ParentStackName: Description: The Stack that created the Transit Gateway Type: String CreateTestInstance: Description: Environment type. Default: 'Yes' Type: String AllowedValues: - 'Yes' - 'No' ConstraintDescription: must specify Yes or No. Conditions: TwoAZs: !Not [!Equals [ !Ref SubnetCidrBlock2, '' ]] CreateTestInstanceCond: !Equals [ !Ref CreateTestInstance, 'Yes' ] CreateTestInstanceCond2: !And [!Equals [ !Ref CreateTestInstance, 'Yes' ], !Not [!Equals [ !Ref SubnetCidrBlock2, '' ]]] Resources: VPC: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref CidrBlock Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: !Sub Spoke-${AWS::StackName} ProtectedSubnet: Type: AWS::EC2::Subnet Properties: VpcId: !Ref 'VPC' CidrBlock: !Ref SubnetCidrBlock AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: !Sub Protected-AZ1-${AWS::StackName} ProtectedSubnet2: Type: AWS::EC2::Subnet Condition: TwoAZs Properties: VpcId: !Ref 'VPC' CidrBlock: !Ref SubnetCidrBlock2 AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: "" Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: !Sub Protected-AZ2-${AWS::StackName} ProtectedRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref 'VPC' Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: !Sub Protected-${AWS::StackName} SubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref 'ProtectedSubnet' RouteTableId: !Ref 'ProtectedRouteTable' Subnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Condition: TwoAZs Properties: SubnetId: !Ref 'ProtectedSubnet2' RouteTableId: !Ref 'ProtectedRouteTable' NetworkAcl: Type: AWS::EC2::NetworkAcl Properties: VpcId: !Ref 'VPC' Tags: - Key: Application Value: !Ref 'AWS::StackId' InboundHTTPNetworkAclEntry: Type: AWS::EC2::NetworkAclEntry Properties: NetworkAclId: !Ref 'NetworkAcl' RuleNumber: '100' Protocol: '-1' RuleAction: allow Egress: 'false' CidrBlock: 0.0.0.0/0 OutboundHTTPNetworkAclEntry: Type: AWS::EC2::NetworkAclEntry Properties: NetworkAclId: !Ref 'NetworkAcl' RuleNumber: '100' Protocol: '-1' RuleAction: allow Egress: 'true' CidrBlock: 0.0.0.0/0 PublicSecurityVpcSubnetNetworkAclAssociation: Type: AWS::EC2::SubnetNetworkAclAssociation Properties: SubnetId: !Ref 'ProtectedSubnet' NetworkAclId: !Ref 'NetworkAcl' PublicSecurity2VpcSubnetNetworkAclAssociation: Type: AWS::EC2::SubnetNetworkAclAssociation Condition: TwoAZs Properties: SubnetId: !Ref 'ProtectedSubnet2' NetworkAclId: !Ref 'NetworkAcl' SpokeAttachment: Type: "AWS::EC2::TransitGatewayAttachment" Properties: SubnetIds: !If - TwoAZs - - !Ref 'ProtectedSubnet' - !Ref 'ProtectedSubnet2' - - !Ref 'ProtectedSubnet' Tags: - Key: Name Value: !Sub Spoke-2${AWS::StackName} TransitGatewayId: Fn::ImportValue: !Sub "${ParentStackName}-TransitGateway" VpcId: !Ref VPC SpokeTransitGatewayRoutePropagation: Type: "AWS::EC2::TransitGatewayRouteTablePropagation" Properties: TransitGatewayAttachmentId: !Ref SpokeAttachment TransitGatewayRouteTableId: Fn::ImportValue: !Sub "${ParentStackName}-TgwRouteTable" SecurityVpcTransitGatewayRoutePropagation: Type: "AWS::EC2::TransitGatewayRouteTablePropagation" Properties: TransitGatewayAttachmentId: !Ref SpokeAttachment TransitGatewayRouteTableId: Fn::ImportValue: !Sub "${ParentStackName}-TgwSecurityVpcRouteTable" SpokeVpcTgwAssociation: Type: "AWS::EC2::TransitGatewayRouteTableAssociation" Properties: TransitGatewayAttachmentId: !Ref SpokeAttachment TransitGatewayRouteTableId: Fn::ImportValue: !Sub "${ParentStackName}-TgwRouteTable" SpokeSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref VPC GroupDescription: Enable SSH access via port 22 SecurityGroupIngress: - IpProtocol: -1 FromPort: -1 ToPort: -1 CidrIp: '10.0.0.0/8' Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: !Sub SpokeSecurityGroup-${AWS::StackName} SpokeRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: ec2.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM SpokeProfile: Type: "AWS::IAM::InstanceProfile" Properties: Roles: - !Ref SpokeRole UpdateTgwRoutesFunctionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonEC2FullAccess - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole UpdateTgwRoutesFunction: Type: 'AWS::Lambda::Function' Properties: Handler: index.process_cfn Runtime: python2.7 Description: '' MemorySize: 512 Timeout: 240 Role: !GetAtt UpdateTgwRoutesFunctionRole.Arn Code: ZipFile: | import boto3 import random import string import uuid import httplib import urlparse import json import base64 import hashlib import os import cfnresponse ec2 = boto3.resource('ec2') ec2client = boto3.client('ec2') def lambda_handler(event, context): try: return process_cfn(event, context) except Exception as e: print("EXCEPTION", e) send_response(event, { 'StackId': event['StackId'], 'RequestId': event['RequestId'], 'LogicalResourceId': event['LogicalResourceId'] }, "FAILED") def process_cfn(event, context): print("Received event: " + json.dumps(event, indent=2)) tgw = event['ResourceProperties']['TransitGateway'] spokeVpcObj = ec2.Vpc(event['ResourceProperties']['SpokeVpc']) securityVpcObj = ec2.Vpc(event['ResourceProperties']['SecurityVpc']) rts = [] rts.append(event['ResourceProperties']['SecurityPrivateRouteTable']) rts.append(event['ResourceProperties']['SecurityPrivateRouteTable2']) rts.append(event['ResourceProperties']['SecurityProtectedRouteTable']) rts.append(event['ResourceProperties']['SecurityProtectedRouteTable2']) spokeRt = event['ResourceProperties']['SpokeProtectedRouteTable'] cidr = spokeVpcObj.cidr_block if event['RequestType'] == 'Delete': for rt in rts: if rt != "SingleAZ": ec2client.delete_route( DestinationCidrBlock= cidr, RouteTableId=rt ) delete_all_routes(spokeRt) responseData = {} responseData['Data'] = "Delete Succcessful" return cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) for rt in rts: if rt != "SingleAZ": ec2.RouteTable(rt).create_route( DestinationCidrBlock=cidr, GatewayId=tgw ) spoke_route_table = ec2.RouteTable(spokeRt) cidr = securityVpcObj.cidr_block route = spoke_route_table.create_route( DestinationCidrBlock=cidr, GatewayId=tgw ) route = spoke_route_table.create_route( DestinationCidrBlock='0.0.0.0/0', GatewayId=tgw ) responseData = {} responseData['Data'] = "Create Succcessful" return cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) def delete_all_routes(routeTableId): source_route_table = ec2.RouteTable(routeTableId) for route in source_route_table.routes: if route.origin == "CreateRoute": response = ec2client.delete_route( DestinationCidrBlock= route.destination_cidr_block, RouteTableId=routeTableId ) return "" UpdateTgwRoutes: Type: Custom::UpdateTgwRoutes DependsOn: - UpdateTgwRoutesFunction - SpokeTransitGatewayRoutePropagation - SpokeVpcTgwAssociation Properties: ServiceToken: !GetAtt UpdateTgwRoutesFunction.Arn SecurityVpc: Fn::ImportValue: !Sub "${ParentStackName}-SecurityVpc" SpokeVpc: !Ref VPC SecurityPrivateRouteTable: Fn::ImportValue: !Sub "${ParentStackName}-SecurityPrivateRouteTable" SecurityProtectedRouteTable: Fn::ImportValue: !Sub "${ParentStackName}-SecurityProtectedRouteTable" SecurityPrivateRouteTable2: Fn::ImportValue: !Sub "${ParentStackName}-SecurityPrivateRouteTable2" SecurityProtectedRouteTable2: Fn::ImportValue: !Sub "${ParentStackName}-SecurityProtectedRouteTable2" SpokeProtectedRouteTable: !Ref ProtectedRouteTable TransitGateway: Fn::ImportValue: !Sub "${ParentStackName}-TransitGateway" SpokeTestInstance: Type: AWS::EC2::Instance Condition: CreateTestInstanceCond DependsOn: - UpdateTgwRoutes Metadata: Comment: Test the domain whitelisting with this instance Properties: ImageId: Fn::ImportValue: !Sub "${ParentStackName}-ImageId" InstanceType: 't2.micro' IamInstanceProfile: !Ref 'SpokeProfile' Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: !Sub SpokeInstance-${AWS::StackName} NetworkInterfaces: - GroupSet: - !Ref 'SpokeSecurityGroup' DeviceIndex: '0' DeleteOnTermination: 'true' SubnetId: !Ref 'ProtectedSubnet' UserData: Fn::Base64: !Sub | #!/bin/bash yum update -y # -e $? #/opt/aws/bin/cfn-signal --stack ${AWS::StackName} --resource SpokeTestInstance --region ${AWS::Region} SpokeTestInstance2: Type: AWS::EC2::Instance Condition: CreateTestInstanceCond2 DependsOn: - UpdateTgwRoutes Metadata: Comment: Test the domain whitelisting with this instance Properties: ImageId: Fn::ImportValue: !Sub "${ParentStackName}-ImageId" InstanceType: 't2.micro' IamInstanceProfile: !Ref 'SpokeProfile' Tags: - Key: Application Value: !Ref 'AWS::StackId' - Key: Name Value: !Sub SpokeInstance-${AWS::StackName} NetworkInterfaces: - GroupSet: - !Ref 'SpokeSecurityGroup' DeviceIndex: '0' DeleteOnTermination: 'true' SubnetId: !Ref 'ProtectedSubnet2' UserData: Fn::Base64: !Sub | #!/bin/bash yum update -y # -e $? #/opt/aws/bin/cfn-signal --stack ${AWS::StackName} --resource SpokeTestInstance --region ${AWS::Region} Outputs: SpokeSecurityGroup: Value: !Ref SpokeSecurityGroup SpokeProtectedRouteTable: Value: !Ref ProtectedRouteTable SpokeAttachment: Value: !Ref SpokeAttachment Description: The spoke attachment to the TransitGateway VpcId: Value: !Ref VPC Description: The VPC ID for the security VPC.