CloudFormation 101 – Part 03

This is part 03 in this series. Previously in

Part 01 – I created a VPC, and internet gateway and attached the internet gateway to the VPC

Part 02 – I added a public and private subnet in one availability zone

In this part, I am adding
– an EIP for the NAT gateway,
– a NAT gateway
– public and private route tables with default routes, and
– associating the route tables with their respective subnets.

The complete (includes all parts 01-03) code is below:

AWSTemplateFormatVersion: '2010-09-09'
#
## The Description section (optional) enables you to include comments about your template.
#
Description:  
  Create VPC, and related components
#
## Parameters section to customize your templates
#
Parameters:
  VPCName:
    Description: Name of the VPC
    Type: String
    Default: "MyVPC"    
    MinLength: '1'
    MaxLength: '30'
    AllowedPattern: '^[a-zA-Z]+[0-9a-zA-Z\-]*$'
    ConstraintDescription: Must contain alphabets and/or numbers.

  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.0.0.0/16    
    MinLength: '10'
    MaxLength: '18'
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range of the form x.x.x.x/x.

  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.1.0/24
    MinLength: '10'
    MaxLength: '18'
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range of the form x.x.x.x/x.

  PrivateSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.3.0/24
    MinLength: '10'
    MaxLength: '18'
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range of the form x.x.x.x/x.

#
## Resources created by the stack
#
Resources:
  #
  ## Create the VPC
  ##
  ## Uses the intrinsic function Ref to get the value of the VPC Name
  ## from parameters above
  #
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref VPCName
  #
  ## Create the IGW
  #
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref VPCName
  #
  ## Connect the IGW to the VPC
  #
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC
  #
  ## Create a public subnet
  ##
  ## The VpcId is obtained by referring back to the VPC created above
  ##
  ## The CIDR block is from the parameters
  ##
  ## The Availability Zone is obtained by querying the available availability
  ## zones in this region and returning the first (offset 0) entry
  ##
  ## The MapPublicIpOnLaunch is set to true indicating that instances launched 
  ## in this subnet receive a public IPv4 address
  #
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref PublicSubnet1CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} Public Subnet (AZ1)
  #
  ## Create a private subnet
  ##
  ## The MapPublicIpOnLaunch is set to false indicating that instances launched 
  ## in this subnet will not receive a public IPv4 address
  #
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs  '' ]
      CidrBlock: !Ref PrivateSubnet1CIDR
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} Private Subnet (AZ1)

  #
  ## Create an Elastic IP (EIP) address
  ##
  ## The below section also uses the DependsOn attribute. With the DependsOn 
  ## attribute you can specify that the creation of a specific resource follows 
  ## another. When you add a DependsOn attribute to a resource, that resource is 
  ## created only after the creation of the resource specified in the DependsOn 
  ## attribute. In this case the NAT Gateway is dependent on the prior creation 
  ## of the InternetGatewayAttachment and the attachment to the VPC 
  #
  NatGateway1EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} NatGateway1 EIP

  #
  ## Create a NAT GW
  ##  
  ## Connect the EIP created above to the NAT GW
  ##
  ## Default to public connectivity
  ##
  ## Connecting to the subnet by using the parameter
  #
  NatGateway1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway1EIP.AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} NatGateway 1

  #
  ## Create a public route table
  #
  PublicRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} Public Routes
  #
  ## Add a public route in the above route table to allow the 
  ## subnets to access the internet through the IGW
  #
  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  #
  ## Associate the public route tables with the public subnets
  #
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable1
      SubnetId: !Ref PublicSubnet1

  #
  ## Create a private route table
  #
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} Private Routes (AZ1)
  #
  ## Add a route in the above route table to allow the 
  ## subnets to access the internet through the IGW
  #
  DefaultPrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  #
  ## Associate the private route tables with the private subnets
  #
  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1

#
## Resources created by the stack
##
## Uses the intrinsic function Sub to get the stack name 
## from parameters above and substitute it into the name of
## the internet gateway
#          
Outputs:
  VPC:
    Description: Name of the VPC
    Value: !Ref VPC
    Export:
      Name: !Sub '${AWS::StackName}'

  InternetGateway:
    Description: Internet Gateway 
    Value: !Ref InternetGateway
    Export:
      Name: !Sub '${AWS::StackName}-InternetGateway'

  PublicSubnet1:
    Description: AZ1 - public subnet
    Value: !Ref PublicSubnet1
    Export:
      Name: !Sub '${AWS::StackName}-PublicSubnet1'
      
  PrivateSubnet1:
    Description: AZ1 - private subnet 01
    Value: !Ref PrivateSubnet1
    Export:
      Name: !Sub '${AWS::StackName}-PrivateSubnet1'
      
  NatGateway1EIP:
    Description: NAT Gateway EIP
    Value: !Ref NatGateway1EIP
    Export:
      Name: !Sub '${AWS::StackName}-NatGateway1EIP'
      
  NatGateway1:
    Description: NAT Gateway 1
    Value: !Ref NatGateway1
    Export:
      Name: !Sub '${AWS::StackName}-NatGateway1'

  PublicRouteTable1:
    Description: Public route table
    Value: !Ref PublicRouteTable1
    Export:
      Name: !Sub '${AWS::StackName}-PublicRouteTable1'

  PrivateRouteTable1:
    Description: Private route table - AZ1 - private subnet 01
    Value: !Ref PrivateRouteTable1
    Export:
      Name: !Sub '${AWS::StackName}-PrivateRouteTable1'

CloudFormation – Create and customize a windows server using user data

This blog demonstrates how to

  • create a windows server via CloudFormation
  • create a user directory
  • install the aws cli
  • add the aws cli to the windows path
  • Create a log file to track the output of the commands executed in the user data section
  • Download a file from an s3 bucket

By using these commands, you can download and install software after the Windows instance is created.

The IAM profile assigned to the EC2 instance should have access to the s3 bucket used.

The code is below:

AWSTemplateFormatVersion: 2010-09-09
Description: Innovate - Create App and Web Servers
Resources:
  rMyBastionInstance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: 'ami-I-want-to-use'
      KeyName: 'my_windows_key'
      IamInstanceProfile: 'My-IAM-profile'
      InstanceType: 'm4.2xlarge'
      SecurityGroupIds: 
        - sg-security-group-id
      SubnetId: 'my-subnet-id'
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
                VolumeType: gp2
                DeleteOnTermination: true
                VolumeSize: 100
                Encrypted: true
      UserData:
        'Fn::Base64': !Sub |
          <script>
              cd \
              mkdir tempdc
              msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2.msi /qn
              cd \tempdc
              echo Current date and time > c:\tempdc\s3_output.txt
              echo %DATE% %TIME% >> c:\tempdc\s3_output.txt
              SET PATH=%PATH%;C:\Program Files\Amazon\AWSCLIV2
              echo %PATH% >> c:\tempdc\s3_output.txt
              aws s3 cp s3://mybucket/my-file.exe c:\tempdc\my-file.exe >> c:\tempdc\s3_output.txt
          </script>
      Tags:
        - Key: Name
          Value: MyWindowsInstance