# Following example shows how to create ingress VPC to inspect ingress traffic # using VPC more specific routing (MSR) and AWS Gateway Load Balancer (GWLB) # The template creates: # - 1 VPC # - 1 IGW # - 3 subnets in AZ1: One each for application1, GWLBE1 and ALB + NAT GW1 # - 3 subnets in AZ2: One each for application2, GWLBE2 and ALB + NAT GW2 # - 6 route tables: One for each subnet. # - 1 bastion security group # - port 22, port 80 and port 443 access # - All TCP, UDP and ICMP from VPC CIDR # - 1 application security group # - All TCP, UDP and ICMP from VPC CIDR # - 1 Application Load Balancer with target group and listener # - 2 Amazon Linux 2 instance acting as applications in application1 and application2 subnet # - Conditionally creates 1 Amazon Linux 2 instance acting as bastion host in public subnet AWSTemplateFormatVersion: "2010-09-09" Description: >- AWS CloudFormation sample template for Ingress VPC setup For Gateway Load Balancer (GWLB) in a distributed architecture across two Availability Zones (AZ). **WARNING** This template creates one or more Amazon EC2 instances, GWLB endpoints, interface endpoints, NAT gateways and ALB. You will be billed or the AWS resources used if you create a stack from this template. Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Network Configuration Parameters: - VpcCidr - AvailabilityZone1 - PublicSubnet1Cidr - GwlbeSubnet1Cidr - ApplicationSubnet1Cidr - AvailabilityZone2 - PublicSubnet2Cidr - GwlbeSubnet2Cidr - ApplicationSubnet2Cidr - Label: default: ALB Configuration Parameters: - ALBName - ALBScheme - Label: default: Target Group Configuration Parameters: - TargetGroupName - ListenerPort - ListenerProtocol - Label: default: Application Configuration Parameters: - ApplicationInstanceType - ApplicationAmiId - ApplicationInstanceDiskSize - KeyPairName - AccessLocation - Label: default: GWLB Endpoint Configuration Parameters: - ServiceName - Label: Bastion Host Condition - CreateBastionHost ParameterLabels: VpcCidr: default: Network CIDR block for new VPC AvailabilityZone1: default: Public Availability Zone 1 PublicSubnet1Cidr: default: Network CIDR for Public Subnet 1 GwlbeSubnet1Cidr: default: Network CIDR for GWLBE Subnet 1 ApplicationSubnet1Cidr: default: Network CIDR for Application Subnet 1 AvailabilityZone2: default: Public Availability Zone 2 PublicSubnet2Cidr: default: Network CIDR for Public Subnet 2 GwlbeSubnet2Cidr: default: Network CIDR for GWLBE Subnet 2 ApplicationSubnet2Cidr: default: Network CIDR for Application Subnet 2 ALBName: default: Application Load Balancer Name ALBScheme: default: Application Load Balancer Scheme TargetGroupName: default: Target Group Name ListenerPort: default: Listener Port ListenerProtocol: default: Listener Protocol ApplicationInstanceType: default: Application Instance Type ApplicationAmiId: default: Latest AMI ID for application (ec2 instance) ApplicationInstanceDiskSize: default: Application Instance Size in GB KeyPairName: default: KeyPair required for accessing Application instance AccessLocation: default: Network CIDR to access Application instance ServiceName: default: The name of the endpoint service to create GWLB endpoint for CreateBastionHost: default: Condition to create bastion host Parameters: # VPC: VpcCidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 10.1.0.0/16 Description: CIDR block for the VPC Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/25 # AZ 1: AvailabilityZone1: Description: Availability Zone to use for the Public Subnet 1 in the VPC Type: AWS::EC2::AvailabilityZone::Name ConstraintDescription: Valid Availability Zone Id PublicSubnet1Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 10.1.11.0/24 Description: CIDR block for the Public Subnet 1 located in Availability Zone 1 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 GwlbeSubnet1Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 10.1.10.0/28 Description: CIDR block for the GWLBE Subnet 1 located in Availability Zone 1 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 ApplicationSubnet1Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 10.1.21.0/24 Description: CIDR block for the Application Subnet 1 located in Availability Zone 1 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 # AZ 2: AvailabilityZone2: Description: Availability Zone to use for the Public Subnet 2 in the VPC Type: AWS::EC2::AvailabilityZone::Name ConstraintDescription: Valid Availability Zone Id PublicSubnet2Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 10.1.12.0/24 Description: CIDR block for the Public Subnet 2 located in Availability Zone 1 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 GwlbeSubnet2Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 10.1.10.16/28 Description: CIDR block for the GWLBE Subnet 2 located in Availability Zone 2 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 ApplicationSubnet2Cidr: AllowedPattern: "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\\/(1[6-9]|2[0-8]))$" Default: 10.1.22.0/24 Description: CIDR block for the Application Subnet 2 located in Availability Zone 2 Type: String ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 # ALB: ALBName: Description: >- Application Load balancer name. This name must be unique within your AWS account and can have a maximum of 32 alphanumeric characters and hyphens. A name cannot begin or end with a hyphen. Type: String Default: alb-ext ConstraintDescription: Must be a valid ALB Name ALBScheme: Description: >- Application Load Balancer scheme. Valid values are internet-facing and internal. The default is internal Default: internet-facing AllowedValues: ['internet-facing', 'internal'] Type: String ConstraintDescription: 'Must be a valid ELB scheme' TargetGroupName: Description: Target Group Name Type: String Default: tg-alb-ext ConstraintDescription: Must be a valid target group name ListenerPort: Description: >- The port on which the listener listens for requests. Default is 80 Default: 80 Type: Number ConstraintDescription: Must be a valid port number ListenerProtocol: Description: Protocol used by application. Default is HTTP Default: HTTP AllowedValues: ['HTTP', 'HTTPS'] Type: String ConstraintDescription: Must be a valid protocol # EC2 Instance: ApplicationInstanceType: Description: Select EC2 instance type for Application instance. Default is set to t2.micro Default: t2.micro AllowedValues: - t2.micro Type: String ApplicationAmiId: Type: AWS::SSM::Parameter::Value Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2' ApplicationInstanceDiskSize: Description: Application instance disk size in GB. Default is set to 8GB Default: 8 AllowedValues: [8] Type: Number ConstraintDescription: Should be a valid instance size in GB KeyPairName: Description: EC2 KeyPair required for accessing EC2 instance Type: AWS::EC2::KeyPair::KeyName ConstraintDescription: Must be the name of an existing EC2 KeyPair AccessLocation: Description: >- Enter desired Network CIDR to access Bastion Host. Default is set to access from anywhere (0.0.0.0/0) and it is not recommended AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})" MinLength: "9" MaxLength: "18" Default: 0.0.0.0/0 Type: String ConstraintDescription: Must be a valid Network CIDR of the form x.x.x.x/y # GWLB Endpoint: ServiceName: Description: >- Enter the name of the service for which you want to create gateway load balancer endpoint. Example service name: com.amazonaws.vpce.us-west-2.vpce-svc-0a76331bc5d6cc4cd Type: String ConstraintDescription: Must be a valid service name # Condition: CreateBastionHost: Description: "Create bastion host? Allowed values: Yes/No, default set to No" Default: "Yes" AllowedValues: ["Yes", "No"] Type: String ConstraintDescription: Value must be either Yes or No Conditions: BastionHostCreate: !Equals [!Ref CreateBastionHost, "Yes"] Resources: # Ingress VPC: Vpc: Type: AWS::EC2::VPC Properties: CidrBlock: !Ref VpcCidr EnableDnsSupport: true EnableDnsHostnames: true InstanceTenancy: default Tags: - Key: Name Value: !Sub "${AWS::StackName}-vpc" # IGW associated with Ingress VPC: InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub "${AWS::StackName}-igw" AttachInternetGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref Vpc InternetGatewayId: !Ref InternetGateway # Subnets: # AZ 1: PublicSubnet1: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Ref AvailabilityZone1 CidrBlock: !Ref PublicSubnet1Cidr VpcId: !Ref Vpc MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub "${AWS::StackName}-public-subnet-1" GwlbeSubnet1: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Ref AvailabilityZone1 CidrBlock: !Ref GwlbeSubnet1Cidr VpcId: !Ref Vpc MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub "${AWS::StackName}-gwlbe-subnet-1" ApplicationSubnet1: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Ref AvailabilityZone1 CidrBlock: !Ref ApplicationSubnet1Cidr VpcId: !Ref Vpc MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub "${AWS::StackName}-app-subnet-1" # AZ 2: PublicSubnet2: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Ref AvailabilityZone2 CidrBlock: !Ref PublicSubnet2Cidr VpcId: !Ref Vpc MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub "${AWS::StackName}-public-subnet-2" GwlbeSubnet2: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Ref AvailabilityZone2 CidrBlock: !Ref GwlbeSubnet2Cidr VpcId: !Ref Vpc MapPublicIpOnLaunch: true Tags: - Key: Name Value: !Sub "${AWS::StackName}-gwlbe-subnet-2" ApplicationSubnet2: Type: AWS::EC2::Subnet Properties: AvailabilityZone: !Ref AvailabilityZone2 CidrBlock: !Ref ApplicationSubnet2Cidr VpcId: !Ref Vpc MapPublicIpOnLaunch: false Tags: - Key: Name Value: !Sub "${AWS::StackName}-app-subnet-2" # Elastic IP and NAT GW: # AZ 1: NATGW1EIP: Type: AWS::EC2::EIP Properties: Domain: vpc NATGW1: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NATGW1EIP.AllocationId SubnetId: !Ref PublicSubnet1 Tags: - Key: Name Value: !Sub "${AWS::StackName}-natgw-1" # AZ 2: NATGW2EIP: Type: AWS::EC2::EIP Properties: Domain: vpc NATGW2: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt NATGW2EIP.AllocationId SubnetId: !Ref PublicSubnet2 Tags: - Key: Name Value: !Sub "${AWS::StackName}-natgw-2" # Gateway Load Balancer endpoints: GwlbVpcEndpoint1: Type: AWS::EC2::VPCEndpoint Properties: VpcId: !Ref Vpc ServiceName: !Ref ServiceName VpcEndpointType: GatewayLoadBalancer SubnetIds: - !Ref GwlbeSubnet1 GwlbVpcEndpoint2: Type: AWS::EC2::VPCEndpoint Properties: VpcId: !Ref Vpc ServiceName: !Ref ServiceName VpcEndpointType: GatewayLoadBalancer SubnetIds: - !Ref GwlbeSubnet2 # Route Table and subnet association: # AZ 1: PublicRouteTable1: Type: AWS::EC2::RouteTable DependsOn: GwlbVpcEndpoint1 Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub "${AWS::StackName}-public-rtb-1" PublicSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet1 RouteTableId: !Ref PublicRouteTable1 GwlbeRouteTable1: Type: AWS::EC2::RouteTable DependsOn: NATGW1 Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub "${AWS::StackName}-gwlbe-rtb-1" GwlbeSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref GwlbeSubnet1 RouteTableId: !Ref GwlbeRouteTable1 ApplicationRouteTable1: Type: AWS::EC2::RouteTable DependsOn: ['GwlbVpcEndpoint1', 'GwlbVpcEndpoint2'] Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub "${AWS::StackName}-app-rtb-1" ApplicationSubnet1RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref ApplicationSubnet1 RouteTableId: !Ref ApplicationRouteTable1 # AZ 2: PublicRouteTable2: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub "${AWS::StackName}-public-rtb-2" PublicSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref PublicSubnet2 RouteTableId: !Ref PublicRouteTable2 GwlbeRouteTable2: Type: AWS::EC2::RouteTable DependsOn: NATGW2 Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub "${AWS::StackName}-gwlbe-rtb-2" GwlbeSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref GwlbeSubnet2 RouteTableId: !Ref GwlbeRouteTable2 ApplicationRouteTable2: Type: AWS::EC2::RouteTable DependsOn: ['GwlbVpcEndpoint1', 'GwlbVpcEndpoint2'] Properties: VpcId: !Ref Vpc Tags: - Key: Name Value: !Sub "${AWS::StackName}-app-rtb-2" ApplicationSubnet2RouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref ApplicationSubnet2 RouteTableId: !Ref ApplicationRouteTable2 # Security Group: BastionSg: Type: AWS::EC2::SecurityGroup Condition: BastionHostCreate Properties: VpcId: !Ref Vpc GroupName: !Sub "${AWS::StackName}-bastion-sg" GroupDescription: >- Access to bastion instance: allow SSH and ICMP access from appropriate location. Allow all traffic from VPC CIDR SecurityGroupIngress: - CidrIp: !Ref AccessLocation IpProtocol: tcp FromPort: 22 ToPort: 22 - CidrIp: !Ref AccessLocation IpProtocol: tcp FromPort: 80 ToPort: 80 - CidrIp: !Ref AccessLocation IpProtocol: tcp FromPort: 443 ToPort: 443 - CidrIp: !Ref AccessLocation IpProtocol: ICMP FromPort: -1 ToPort: -1 - CidrIp: !Ref VpcCidr IpProtocol: "-1" FromPort: -1 ToPort: -1 SecurityGroupEgress: - CidrIp: 0.0.0.0/0 IpProtocol: "-1" FromPort: -1 ToPort: -1 Tags: - Key: Name Value: !Sub "${AWS::StackName}-bastion-sg" ALBSg: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref Vpc GroupName: !Sub "${AWS::StackName}-alb-sg" GroupDescription: >- Access to ALB: allows HTTP, HTTPS and ICMP access from anywhere. Allow all traffic from VPC CIDR SecurityGroupIngress: - CidrIp: !Ref AccessLocation IpProtocol: tcp FromPort: 80 ToPort: 80 - CidrIp: !Ref AccessLocation IpProtocol: tcp FromPort: 443 ToPort: 443 - CidrIp: !Ref AccessLocation IpProtocol: ICMP FromPort: -1 ToPort: -1 - CidrIp: !Ref VpcCidr IpProtocol: "-1" FromPort: -1 ToPort: -1 SecurityGroupEgress: - CidrIp: 0.0.0.0/0 IpProtocol: "-1" FromPort: -1 ToPort: -1 Tags: - Key: Name Value: !Sub "${AWS::StackName}-alb-sg" ApplicationSg: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref Vpc GroupName: !Sub "${AWS::StackName}-app-sg" GroupDescription: >- Access to application instance: Allow access from only ALB. SecurityGroupIngress: - CidrIp: !Ref VpcCidr IpProtocol: "-1" FromPort: -1 ToPort: -1 SecurityGroupEgress: - CidrIp: 0.0.0.0/0 IpProtocol: "-1" FromPort: -1 ToPort: -1 Tags: - Key: Name Value: !Sub "${AWS::StackName}-app-sg" # IAM Instance Role and Profile: InstanceSSMRole: Type: AWS::IAM::Role Properties: RoleName: !Sub "${AWS::StackName}-instance-ssm-role" ManagedPolicyArns: - "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" AssumeRolePolicyDocument: Statement: - Effect: Allow Principal: Service: - ec2.amazonaws.com Action: - 'sts:AssumeRole' Path: / InstanceSSMProfile: Type: AWS::IAM::InstanceProfile Properties: Path: / Roles: - !Ref InstanceSSMRole # EC2 Instances (servers and bastion host): Server1: DependsOn: - PublicRtb1EditLocalRoute - GwlbeRtb1Route1 - AppRtb1Route1 - AppRtb1Route2 - AppRtb1EditLocalRoute Type: AWS::EC2::Instance Properties: ImageId: !Ref ApplicationAmiId KeyName: !Ref KeyPairName InstanceType: !Ref ApplicationInstanceType IamInstanceProfile: !Ref InstanceSSMProfile SecurityGroupIds: - !Ref ApplicationSg SubnetId: !Ref ApplicationSubnet1 BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: VolumeSize: !Ref ApplicationInstanceDiskSize Tags: - Key: Name Value: !Sub "${AWS::StackName}-server-1" UserData: Fn::Base64: | #!/bin/bash -ex # Install packages: yum update -y; yum install htop -y; yum install httpd -y; # Configure hostname: hostnamectl set-hostname gwlbe-ingress-vpc-server-1; # Configure SSH client alive interval for ssh session timeout: echo 'ClientAliveInterval 60' | sudo tee --append /etc/ssh/sshd_config; service sshd restart; # Set dark background for vim: touch /home/ec2-user/.vimrc; echo "set background=dark" >> /home/ec2-user/.vimrc; # Define variables: curl http://169.254.169.254/latest/dynamic/instance-identity/document > /home/ec2-user/iid export instance_az=$(cat /home/ec2-user/iid |grep 'availability' | awk -F': ' '{print $2}' | awk -F',' '{print $1}'); # Start httpd and configure index.html: systemctl enable httpd; systemctl start httpd; touch /var/www/html/index.html echo "" >> /var/www/html/index.html echo " " >> /var/www/html/index.html echo " Gateway Load Balancer Endpoint" >> /var/www/html/index.html echo " " >> /var/www/html/index.html echo " " >> /var/www/html/index.html echo " " >> /var/www/html/index.html echo "

GWLB Endpoint POC:

" >> /var/www/html/index.html echo "

Welcome to Ingress VPC: GWLB Endpoint + ALB + VPC Routing Enhancements POC:

" >> /var/www/html/index.html echo "

This is server 1 running in $instance_az. Happy testing!

" >> /var/www/html/index.html echo " " >> /var/www/html/index.html echo "" >> /var/www/html/index.html Server2: DependsOn: - PublicRtb2EditLocalRoute - GwlbeRtb2Route1 - AppRtb2Route1 - AppRtb2Route2 - AppRtb2EditLocalRoute Type: AWS::EC2::Instance Properties: ImageId: !Ref ApplicationAmiId KeyName: !Ref KeyPairName InstanceType: !Ref ApplicationInstanceType IamInstanceProfile: !Ref InstanceSSMProfile SecurityGroupIds: - !Ref ApplicationSg SubnetId: !Ref ApplicationSubnet2 BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: VolumeSize: !Ref ApplicationInstanceDiskSize Tags: - Key: Name Value: !Sub "${AWS::StackName}-server-2" UserData: Fn::Base64: | #!/bin/bash -ex # Install packages: yum update -y; yum install htop -y; yum install httpd -y; # Configure hostname: hostnamectl set-hostname gwlbe-ingress-vpc-server-2; # Configure SSH client alive interval for ssh session timeout: echo 'ClientAliveInterval 60' | sudo tee --append /etc/ssh/sshd_config; service sshd restart; # Set dark background for vim: touch /home/ec2-user/.vimrc; echo "set background=dark" >> /home/ec2-user/.vimrc; # Define variables: curl http://169.254.169.254/latest/dynamic/instance-identity/document > /home/ec2-user/iid export instance_az=$(cat /home/ec2-user/iid |grep 'availability' | awk -F': ' '{print $2}' | awk -F',' '{print $1}'); # Start httpd and configure index.html: systemctl enable httpd; systemctl start httpd touch /var/www/html/index.html echo "" >> /var/www/html/index.html echo " " >> /var/www/html/index.html echo " Gateway Load Balancer Endpoint POC" >> /var/www/html/index.html echo " " >> /var/www/html/index.html echo " " >> /var/www/html/index.html echo " " >> /var/www/html/index.html echo "

GWLB Endpoint POC:

" >> /var/www/html/index.html echo "

Welcome to Ingress VPC: GWLB Endpoint + ALB + VPC Routing Enhancements POC:

" >> /var/www/html/index.html echo "

This is server 2 running in $instance_az. Happy testing!

" >> /var/www/html/index.html echo " " >> /var/www/html/index.html echo "" >> /var/www/html/index.html BastionHost: DependsOn: ['GwlbeRtb1Route1', 'PublicRtb1EditLocalRoute'] Type: AWS::EC2::Instance Condition: BastionHostCreate Properties: ImageId: !Ref ApplicationAmiId KeyName: !Ref KeyPairName InstanceType: !Ref ApplicationInstanceType IamInstanceProfile: !Ref InstanceSSMProfile SecurityGroupIds: - !Ref BastionSg SubnetId: !Ref PublicSubnet1 BlockDeviceMappings: - DeviceName: /dev/xvda Ebs: VolumeSize: !Ref ApplicationInstanceDiskSize Tags: - Key: Name Value: !Sub "${AWS::StackName}-bastion-host" UserData: Fn::Base64: | #!/bin/bash -ex # Install packages: yum update -y; yum install htop -y; # Configure hostname: hostnamectl set-hostname gwlbe-ingress-vpc-bh; # Configure SSH client alive interval for ssh session timeout: echo 'ClientAliveInterval 60' | sudo tee --append /etc/ssh/sshd_config; service sshd restart; # Set dark background for vim: touch /home/ec2-user/.vimrc; echo "set background=dark" >> /home/ec2-user/.vimrc; # Create Lambda Custom Resource to edit existing route in an RTB: ReplaceRouteLambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: root PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:* - Effect: Allow Action: - ec2:ReplaceRoute Resource: "*" # Lambda creates CloudWatch Log Group. # Since CF stack didn't explicitly create the Log Group, Log Group doesn't get deleted when stack is deleted. # Hence creating Log Group though the stack for Lambda specific funciton. # Their are few things to consider. For more details refer to: https://github.com/aws/serverless-application-model/issues/1216 ReplaceRouteLogGroup: Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub /aws/lambda/${AWS::StackName}ReplaceRoute RetentionInDays: 1 ReplaceRoute: Type: AWS::Lambda::Function DependsOn: ReplaceRouteLogGroup Properties: FunctionName: !Sub ${AWS::StackName}ReplaceRoute Handler: "index.handler" Role: !GetAtt ReplaceRouteLambdaExecutionRole.Arn Code: ZipFile: | import json import logging import time import boto3 import cfnresponse from botocore.exceptions import ClientError try: ec2 = boto3.client('ec2') except ClientError as e: logger.error(f"ERROR: failed to connect to EC2 client: {e}") sys.exit(1) def handler(event, context): logger = logging.getLogger() logger.setLevel(logging.INFO) logger.info('Received event: {}'.format(json.dumps(event))) responseData = {} responseStatus = cfnresponse.FAILED try: DestCidr = event["ResourceProperties"]["DestCidr"] VpceId = event["ResourceProperties"]["VpceId"] RtbId = event["ResourceProperties"]["RtbId"] except Exception as e: logger.info('Attribute retrival failure: {}'.format(e)) try: if event["RequestType"] == "Delete": responseStatus = cfnresponse.SUCCESS cfnresponse.send(event, context, responseStatus, responseData) except Exception: logger.exception("Signaling failure to CloudFormation.") cfnresponse.send(event, context, cfnresponse.FAILED, {}) if event["RequestType"] == "Create": logger.info(f"Replacing target to {VpceId} for {DestCidr} for {RtbId}") try: response = ec2.replace_route( DestinationCidrBlock = DestCidr, VpcEndpointId = VpceId, RouteTableId = RtbId ) except Exception as e: logger.info('ec2.describe_vpc_endpoint_service_configurations failure: {}'.format(e)) responseStatus = cfnresponse.SUCCESS cfnresponse.send(event, context, responseStatus, responseData) Runtime: python3.7 Timeout: 150 # Add routes to route tables: # AZ 1: # Public route table 1 routes: PublicRtb1Route1: Type: AWS::EC2::Route DependsOn: AttachInternetGateway Properties: DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway RouteTableId: !Ref PublicRouteTable1 PublicRtb1EditLocalRoute: Type: Custom::ReplaceRoute # DependsOn: GwlbVpcEndpoint1 Properties: ServiceToken: !GetAtt ReplaceRoute.Arn DestCidr: !Ref VpcCidr VpceId: !Ref GwlbVpcEndpoint1 RtbId: !Ref PublicRouteTable1 # GWLBE route table 1 routes: GwlbeRtb1Route1: Type: AWS::EC2::Route # DependsOn: NATGW1 Properties: DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NATGW1 RouteTableId: !Ref GwlbeRouteTable1 # Application route table 1 routes: AppRtb1Route1: Type: AWS::EC2::Route # DependsOn: GwlbVpcEndpoint1 Properties: DestinationCidrBlock: 10.1.12.0/24 VpcEndpointId: !Ref GwlbVpcEndpoint2 RouteTableId: !Ref ApplicationRouteTable1 AppRtb1Route2: Type: AWS::EC2::Route # DependsOn: GwlbVpcEndpoint1 Properties: DestinationCidrBlock: 0.0.0.0/0 VpcEndpointId: !Ref GwlbVpcEndpoint1 RouteTableId: !Ref ApplicationRouteTable1 AppRtb1EditLocalRoute: Type: Custom::ReplaceRoute # DependsOn: GwlbVpcEndpoint1 Properties: ServiceToken: !GetAtt ReplaceRoute.Arn DestCidr: !Ref VpcCidr VpceId: !Ref GwlbVpcEndpoint1 RtbId: !Ref ApplicationRouteTable1 # AZ 2: # Public route table 2 routes: PublicRtb2Route1: Type: AWS::EC2::Route DependsOn: AttachInternetGateway Properties: DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway RouteTableId: !Ref PublicRouteTable2 PublicRtb2EditLocalRoute: Type: Custom::ReplaceRoute # DependsOn: GwlbVpcEndpoint2 Properties: ServiceToken: !GetAtt ReplaceRoute.Arn DestCidr: !Ref VpcCidr VpceId: !Ref GwlbVpcEndpoint2 RtbId: !Ref PublicRouteTable2 # GWLBE route table 2 routes: GwlbeRtb2Route1: Type: AWS::EC2::Route # DependsOn: NATGW2 Properties: DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NATGW2 RouteTableId: !Ref GwlbeRouteTable2 # Application route table 2 routes: AppRtb2Route1: Type: AWS::EC2::Route # DependsOn: GwlbVpcEndpoint2 Properties: DestinationCidrBlock: 10.1.11.0/24 VpcEndpointId: !Ref GwlbVpcEndpoint1 RouteTableId: !Ref ApplicationRouteTable2 AppRtb2Route2: Type: AWS::EC2::Route # DependsOn: GwlbVpcEndpoint2 Properties: DestinationCidrBlock: 0.0.0.0/0 VpcEndpointId: !Ref GwlbVpcEndpoint2 RouteTableId: !Ref ApplicationRouteTable2 AppRtb2EditLocalRoute: Type: Custom::ReplaceRoute # DependsOn: GwlbVpcEndpoint2 Properties: ServiceToken: !GetAtt ReplaceRoute.Arn DestCidr: !Ref VpcCidr VpceId: !Ref GwlbVpcEndpoint2 RtbId: !Ref ApplicationRouteTable2 # ALB: ALB: Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Ref ALBName Scheme: !Ref ALBScheme SecurityGroups: - !Ref ALBSg Type: application Subnets: - !Ref PublicSubnet1 - !Ref PublicSubnet2 Tags: - Key: Name Value: !Sub "${AWS::StackName}-alb1" TargetGroup: Type: AWS::ElasticLoadBalancingV2::TargetGroup Properties: Name: !Ref TargetGroupName Port: !Ref ListenerPort Protocol: !Ref ListenerProtocol TargetGroupAttributes: - Key: deregistration_delay.timeout_seconds Value: '20' VpcId: !Ref Vpc HealthCheckPort: '80' HealthCheckProtocol: 'HTTP' TargetType: instance Targets: - Id: !Ref Server1 - Id: !Ref Server2 Tags: - Key: Name Value: !Sub "${AWS::StackName}-alb1-tg1" Listener: Type: AWS::ElasticLoadBalancingV2::Listener Properties: Port: !Ref ListenerPort Protocol: !Ref ListenerProtocol DefaultActions: - Type: forward TargetGroupArn: !Ref TargetGroup LoadBalancerArn: !Ref ALB # Outputs: Outputs: IngressBastionHostPublicIp: Condition: BastionHostCreate Description: Ingress VPC Bastion Instance Public IP Value: !GetAtt BastionHost.PublicIp IngressServer1PrivateIp: Description: Ingress VPC Application Instance Private IP Value: !GetAtt Server1.PrivateIp IngressServer2PrivateIp: Description: Ingress VPC Application Instance Private IP Value: !GetAtt Server2.PrivateIp IngressVpcCidr: Description: Ingress VPC CIDR Value: !Ref VpcCidr IngressVpcId: Description: Ingress VPC ID Value: !Ref Vpc IngressPublicSubnet1Id: Description: Ingress VPC Public Subnet 1 ID Value: !Ref PublicSubnet1 IngressGwlbeSubnet1Id: Description: Ingress VPC GWLBE Subnet 1 ID Value: !Ref GwlbeSubnet1 IngressApplicationSubnet1Id: Description: Ingress VPC Application Subnet 1 ID Value: !Ref ApplicationSubnet1 IngressPublicSubnet2Id: Description: Ingress VPC Public Subnet 2 ID Value: !Ref PublicSubnet2 IngressGwlbeSubnet2Id: Description: Ingress VPC GWLBE Subnet 2 ID Value: !Ref GwlbeSubnet2 IngressApplicationSubnet2Id: Description: Ingress VPC Application Subnet 2 ID Value: !Ref ApplicationSubnet2 IngressAlbSgId: Description: Ingress VPC Application Security Group ID Value: !Ref ALBSg IngressBastionSgId: Condition: BastionHostCreate Description: Ingress VPC Bastion Security Group ID Value: !Ref BastionSg IngressApplicationSgId: Description: Ingress VPC Application Security Group ID Value: !Ref ApplicationSg IngressGwlbVpcEndpoint1Id: Description: Gateway Load Balancer VPC Endpoint 1 ID Value: !Ref GwlbVpcEndpoint1 IngressGwlbVpcEndpoint2Id: Description: Gateway Load Balancer VPC Endpoint 2 ID Value: !Ref GwlbVpcEndpoint2