AWS Cloudformation to create AWS VPC

This blog will build AWS Virtual Private Cloud (VPC) creation using AWS Cloudformation template, step by step. However, it is necessary to plan your network with Classless Inter-Domain Routing (CIDR).



Simplest VPC

Here this simplest workable CFN:

Fig 1: Creates only VPC
Fig 1: Creates only VPC

This creates only the VPC, as shown in the above diagram.

AWSTemplateFormatVersion: "2010-09-09"
Description: My VPC example
Parameters:
  EnvironmentName:
    Description: prefix for the resources
    Type: String
    Default: oj-test

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.192.0.0/24
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

As shown in the CFN, the CIDR block is the same as Network Address Block in Fig 2.

To create the stack

aws cloudformation create-stack --template-body file://test.yaml  --stack-name oj-test-stack

To delete the stack:

aws cloudformation delete-stack --stack-name oj-test-stack

Planning

Using Subnet Calculator Site24x71, you can plan and size the subnets as follows. Each subnet, following addresses are already allocated and not available to general use:

  1. Starting address is Network Address (Eg: 10.192.0.0)
  2. Second address (Network+1) address is allocated for VPC Router (Eg: 10.192.0.1)
  3. Third address (Network+2) is reserved for DNS (Eg: 10.192.0.2)
  4. Fourth address (Network+3) is reserved for future use (Eg: 10.192.0.3)
  5. Broadcast is the last address (Eg: 10.192.0.127)

I am going to have two public subnets (Subnet ID 1 and 2) and two private subnets.

To create above infrastructure:

AWSTemplateFormatVersion: "2010-09-09"
Description: My VPC example
Parameters:
  EnvironmentName:
    Description: prefix for the resources
    Type: String
    Default: oj-test

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.192.0.0/24
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.192.0.0/26
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value:  !Sub ${EnvironmentName} Public Subnet (AZ0) 

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.192.0.64/26
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value:  !Sub ${EnvironmentName} Public Subnet (AZ1)      

  PrivateSubnet1:
      Type: AWS::EC2::Subnet
      Properties:
        CidrBlock: 10.192.0.128/26
        VpcId: !Ref VPC
        AvailabilityZone: !Select [0, !GetAZs '']
        MapPublicIpOnLaunch: false
        Tags:
          - Key: Name
            Value:  !Sub ${EnvironmentName} Private Subnet (AZ0) 
                    
  PrivateSubnet2:
      Type: AWS::EC2::Subnet
      Properties:
        CidrBlock: 10.192.0.192/26
        VpcId: !Ref VPC
        AvailabilityZone: !Select [1, !GetAZs '']
        MapPublicIpOnLaunch: false
        Tags:
          - Key: Name
            Value:  !Sub ${EnvironmentName} Private Subnet (AZ1)          

This is what it creates after the stack created:

Fig 4: Designer
Fig 4: Designer

If you are interested to enable IPv6 CIDR as follows:

AWSTemplateFormatVersion: "2010-09-09"
Description: My VPC example
Parameters:
  EnvironmentName:
    Description: prefix for the resources
    Type: String
    Default: oj-test

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.192.0.0/24
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

  IPv6Cidr:
    Type: AWS::EC2::VPCCidrBlock
    Properties:
      VpcId: !Ref VPC
      AmazonProvidedIpv6CidrBlock: true

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.192.0.0/26
      Ipv6CidrBlock: 
        Fn::Sub:
          - "${VpcPart}${SubnetPart}"
          - SubnetPart: '00::/64' 
            VpcPart: !Select [0, !Split ['00::/56', !Select [0, !GetAtt VPC.Ipv6CidrBlocks]]] 
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value:  !Sub ${EnvironmentName} Public Subnet (AZ0) 

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.192.0.64/26
      Ipv6CidrBlock: 
        Fn::Sub:
          - "${VpcPart}${SubnetPart}"
          - SubnetPart: '01::/64' 
            VpcPart: !Select [0, !Split ['00::/56', !Select [0, !GetAtt VPC.Ipv6CidrBlocks]]]       
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value:  !Sub ${EnvironmentName} Public Subnet (AZ1)      

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.192.0.128/26
      Ipv6CidrBlock: 
        Fn::Sub:
          - "${VpcPart}${SubnetPart}"
          - SubnetPart: '02::/64' 
            VpcPart: !Select [0, !Split ['00::/56', !Select [0, !GetAtt VPC.Ipv6CidrBlocks]]]         
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value:  !Sub ${EnvironmentName} Private Subnet (AZ0) 
                    
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.192.0.192/26
      Ipv6CidrBlock: 
        Fn::Sub:
          - "${VpcPart}${SubnetPart}"
          - SubnetPart: '03::/64' 
            VpcPart: !Select [0, !Split ['00::/56', !Select [0, !GetAtt VPC.Ipv6CidrBlocks]]]          
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value:  !Sub ${EnvironmentName} Private Subnet (AZ1)    

Attach AWS Internet Gateway and Route Table

AWS Subnets are, by default, private. To enable internet access, you have to:

  1. Create an Internet Gateway (IGW) and attach to the VPC
  2. Create Route Table (RT) within the VPC with routes to outside
    1. Default public route for IPv6 route 0.0.0.0/0
    2. Default public route for IPv6 Route ::/0
  3. Attache RT to Subnets which is supposed to be public
  4. Auto-assign IPv4

The template is

AWSTemplateFormatVersion: "2010-09-09"
Description: My VPC example
Parameters:
  EnvironmentName:
    Description: prefix for the resources
    Type: String
    Default: oj-test

Resources:
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.192.0.0/24
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Ref EnvironmentName

  IPv6Cidr:
    Type: AWS::EC2::VPCCidrBlock
    Properties:
      VpcId: !Ref VPC
      AmazonProvidedIpv6CidrBlock: true

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.192.0.0/26
      Ipv6CidrBlock: 
        Fn::Sub:
          - "${VpcPart}${SubnetPart}"
          - SubnetPart: '00::/64' 
            VpcPart: !Select [0, !Split ['00::/56', !Select [0, !GetAtt VPC.Ipv6CidrBlocks]]] 
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      # AssignIpv6AddressOnCreation: true
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value:  !Sub ${EnvironmentName} Public Subnet (AZ0) 

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.192.0.64/26
      Ipv6CidrBlock: 
        Fn::Sub:
          - "${VpcPart}${SubnetPart}"
          - SubnetPart: '01::/64' 
            VpcPart: !Select [0, !Split ['00::/56', !Select [0, !GetAtt VPC.Ipv6CidrBlocks]]]       
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      # AssignIpv6AddressOnCreation: true
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value:  !Sub ${EnvironmentName} Public Subnet (AZ1)      

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.192.0.128/26
      Ipv6CidrBlock: 
        Fn::Sub:
          - "${VpcPart}${SubnetPart}"
          - SubnetPart: '02::/64' 
            VpcPart: !Select [0, !Split ['00::/56', !Select [0, !GetAtt VPC.Ipv6CidrBlocks]]]         
      VpcId: !Ref VPC
      AvailabilityZone: !Select [0, !GetAZs '']
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value:  !Sub ${EnvironmentName} Private Subnet (AZ0) 
                    
  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.192.0.192/26
      Ipv6CidrBlock: 
        Fn::Sub:
          - "${VpcPart}${SubnetPart}"
          - SubnetPart: '03::/64' 
            VpcPart: !Select [0, !Split ['00::/56', !Select [0, !GetAtt VPC.Ipv6CidrBlocks]]]          
      VpcId: !Ref VPC
      AvailabilityZone: !Select [1, !GetAZs '']
      AssignIpv6AddressOnCreation: true
      Tags:
        - Key: Name
          Value:  !Sub ${EnvironmentName} Private Subnet (AZ1)    

  IGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: 
        - Key: Name
          Value: !Ref EnvironmentName

  IGWAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC 
      InternetGatewayId: !Ref IGW 

  RT:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${EnvironmentName} Public Routes
  
  DefaultPublicRoutes:
    Type: AWS::EC2::Route
    DependsOn: IGWAttachment
    Properties:
      RouteTableId: !Ref RT
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref IGW

  PublicSub1NetRTAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RT
      SubnetId: !Ref PublicSubnet1

  PublicSub2NetRTAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RT
      SubnetId: !Ref PublicSubnet2

Testing the VPC

You can create an EC2 instances in public subnet (bastion=jumpbox) and and private subnet and test both subnets using SSH port forwarding to private instance via bastion.

In your local machine (in my case MacOs), you have to verify the permission is 400 of your SSH key use to connect to the bastion. In the MacOS:

stat -f "%OLp" ~/mykey.pem

Verify the SSH-agent is running. In the MacOS:

eval `ssh-agent`

which should give you a process PID.

Add the key to the SSH-agent:

ssh-add -K ~/mykey.pem 

Using agent you can connect to the instance (same AZ where bastion is located) in the private subnet. First create a bastion host in the public subnet of the selected AZ.

  1. Connect to the bastion:

    ssh -A -i "mykey.pem" ec2-user@ec2-.-..-...-173.compute-1.amazonaws.com
    

    The option -A is for agent access and you don't need to specify the location of the key file in the MacOs.

  2. Check the internet connectivity of the bastion by ping:

    ping 1.1.1.1
    

    This should return the 64 bytes continuously.

  3. Inside the bastion, connect to the instance in the private subnet using private address of that instance. This is where SSH agent help you.

    ssh ec2-user@10.192.0.234
    
  4. if you ping, this private instance not return any bytes because it doesn't have any access to internet.

  1. Subnet Calculator for IPV4

Comments

Popular posts from this blog

How To: GitHub projects in Spring Tool Suite

Spring 3 Part 7: Spring with Databases

Parse the namespace based XML using Python