AWS Cloud Formation (example 2)

SSH config

Host bastion
   HostName {bastionPublicIP}
   User ec2-user
   IdentityFile ~/.ssh/bastion.pem
   ProxyCommand none
Host slave
   HostName {instancePrivateIp}
   User ec2-user
   IdentityFile ~/.ssh/instance.pem
   ProxyCommand ssh bastion -W %h:%p

Cloud Formation template

AWSTemplateFormatVersion: 2010-09-09

Parameters:
  BastionKeyName:
    Description: The EC2 Key Pair to allow SSH access to the bastion
    Type: 'AWS::EC2::KeyPair::KeyName'
  InstanceKeyName:
    Description: The EC2 Key Pair to allow SSH access to the instance
    Type: 'AWS::EC2::KeyPair::KeyName'

Resources:

  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.1.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
      - Key: Name
        Value:  !Join ['', [!Ref "AWS::StackName", "-vpc" ]]

  InternetGateway:
    Type: AWS::EC2::InternetGateway
    DependsOn: VPC
    Properties:
      Tags:
      - Key: Name
        Value:  !Join ['', [!Ref "AWS::StackName", "-ig" ]]

  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGateway

# Subnet --------------------------------------------------------------

  PublicSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.1.10.0/24
      AvailabilityZone: !Select [ 0, !GetAZs ]
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-public-a

  PublicSubnetB:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.1.20.0/24
      AvailabilityZone: !Select [ 1, !GetAZs ]
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-public-b

  PrivateSubnetA:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.1.50.0/24
      AvailabilityZone: !Select [ 0, !GetAZs ]
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-private-a

  PrivateSubnetB:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.1.60.0/24
      AvailabilityZone: !Select [ 1, !GetAZs ]
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-private-b

# PublicRouteTable -------------------------------------------------------

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-public
  PublicRoute1:
    Type: AWS::EC2::Route
    DependsOn: AttachGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway

# PrivateRouteTable -------------------------------------------------------

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-private
  PrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NATGateway

# NATGateway -------------------------------------------------------

  NATGateway:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt ElasticIPAddress.AllocationId
      SubnetId: !Ref PublicSubnetA
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-nat
  ElasticIPAddress:
    Type: AWS::EC2::EIP
    Properties:
      Domain: VPC

# SubnetRouteTableAssociation -------------------------------------------------------

  PublicSubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref PublicRouteTable
  PublicSubnetBRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnetB
      RouteTableId: !Ref PublicRouteTable
  PrivateSubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnetA
      RouteTableId: !Ref PrivateRouteTable
  PrivateSubnetBRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnetB
      RouteTableId: !Ref PrivateRouteTable


# SecurityGroup --------------------------------------------------------------------

  BastionSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      VpcId: !Ref VPC
      GroupDescription: Bastion SSH access
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-bastion
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        CidrIp: 0.0.0.0/0

  InstanceSecurityGroup:
    Type: 'AWS::EC2::SecurityGroup'
    Properties:
      VpcId: !Ref VPC
      GroupDescription: Instance access
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-instance
      SecurityGroupIngress:
      - IpProtocol: tcp
        FromPort: '80'
        ToPort: '80'
        CidrIp: 0.0.0.0/0
      - IpProtocol: tcp
        FromPort: '22'
        ToPort: '22'
        "SourceSecurityGroupId": {
          "Fn::GetAtt": [
            "BastionSecurityGroup",
            "GroupId"
          ]
        }

# EC2 --------------------------------------------------------------------

  BastionEc2Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      KeyName: !Ref BastionKeyName
      ImageId: 'ami-035b3c7efe6d061d5'
      NetworkInterfaces:
      - AssociatePublicIpAddress: "true"
        DeviceIndex: "0"
        GroupSet:
        - !Ref BastionSecurityGroup
        SubnetId:
          !Ref PublicSubnetA
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-bastion

  WebEc2Instance:
    Type: 'AWS::EC2::Instance'
    Properties:
      KeyName: !Ref InstanceKeyName
      ImageId: 'ami-035b3c7efe6d061d5'
      NetworkInterfaces:
      - AssociatePublicIpAddress: False
        DeviceIndex: "0"
        GroupSet:
        - !Ref InstanceSecurityGroup
        SubnetId:
          !Ref PrivateSubnetA
      Tags:
      - Key: Name
        Value: !Sub ${AWS::StackName}-web