[CloudFormation] Bastion Host Setup

ยท

5 min read

Context

This is a high-level walkthrough of the YAML definition of the CloudFormation templates used in my article on setting up a Bastion Host. Please refer to this GitHub repository for the CloudFormation templates.

Prerequisites

You will need to have

  • Some basic knowledge in navigating AWS Management Console
  • An existing S3 bucket to be able to run nested CloudFormation stack
  • An IAM account that has the required permission to run CloudFormation
  • An existing KeyPair

Why CloudFormation?

CloudFormation is native to AWS. So there will be minimal installation and/or setup required by the readers, other than having an AWS account.

I understand that the trending IaC tool is Terraform. I am still learning, so perhaps I will write an article for the Terraform equivalent as well!


Shorthands and Pseudo Parameters

In the CloudFormation templates, you will notice the following shorthands such as:

  • !GetAtt is to use the values from Outputs section or an attribute from a resource.
  • !Ref is to return the value of a specific parameter or resource.
  • !Sub is to substitute variables in an input string with values.
  • !FindInMap is to return the value corresponding to keys in a two-level map declared in the Mappings section.

As this is a small-scale usage of CloudFormation, I only use 1 pseudo parameter in the templates, i.e. ${AWS::StackName} which represents the CloudFormation stack name.

Walkthrough of CloudFormation Templates

The CloudFormation templates are main.yml and components.yml.

main.yml

In this CloudFormation template, it is meant to take in the parameters and run the nested stacks. You can think of this script as the wrapper script to run other CloudFormation templates.

This template is known as the master template, which is used to generate the parent stack and create the EC2 instances in the respective subnets.

Mappings Section

This section is optional and used to create unique name-value pairs. The values can be String or List types. You can find it at Line 4 - 11.

In the template, it is used to map the resource specifications (memory and CPU) to the instance class. For this exercise, I have limited the choices so that you will not accidentally set any larger instances that cost much more.

Parameters Section

This section is self-explanatory, i.e. where you can define the required inputs for the template to run. You can observe this when you are creating the CloudFormation Stack. You can find it at Line 13 - 66.

AWS::CloudFormation::Interface

To make the template more user-friendly, I used the AWS::CloudFormation::Interface under the Metadata section. This allows me to group the parameters together and classify them under a more meaningful parameter group label. You can find it at Line 68 - 88.

Resources Section

This is the most important section, where you will be defining what you want CloudFormation to provision for you. The master template will reference the component template, which I named ComponentStack. The TemplateURL property will refer to one of our parameters i.e. ComponentTemplateURL. The ComponentStack is a user-defined nested stack. As we provision EC2 instances in the private and public subnet respectively, you can observe that the mapping for the instance class is being used here along with outputs from the ComponentStack. You can find it from Line 90 onwards.

components.yml

In this CloudFormation template, it will be used to provision the following components that will be utilized by the EC2 instance

  • Private subnet
  • Route table for the private subnet
  • Security groups

Referencing from the ComponentStack defined in main.yml, you can see that there are matching parameters defined in component.yml where the parameters' values are passed from the master template to the component template.

  ComponentStack:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: !Ref ComponentTemplateURL
      Parameters:
        MyPublicIp: !Ref MyPublicIp
        VpcId: !Ref VpcId
        DefaultPublicSubnetCidr: !Ref DefaultPublicSubnetCidr
        PrivateSubnetCidr: !Ref PrivateSubnetCidr
      TimeoutInMinutes: 3

Outputs Section

In this section, you can simply think it as a return value from the component stack. This is useful especially when you have values you want to use in our CloudFormation stack. In the templates, I used to output the security group ID, private subnet ID and private route table association ID.

Usage

High-Level Flow

  1. Clone the repository
# Clone the repository
git clone https://github.com/bernicecpz/hashnode_resources

# Navigate to location of the CloudFormation templates
cd hashnode_resources/aws_series/cf_templates/01_bastionhostsetup/
  1. Create an S3 bucket if not done so.
  2. Upload both files into the S3 bucket created in step 1
  3. Create a new CloudFormation stack
    • Provide the S3 bucket URL for the main.yml file
  4. Upon the completion of the provisioning of the resources via the CloudFormation, you can now access the setup of the EC2 instance.

Create CloudFormation Stack

  1. In the searchbar, enter CloudFormation. Select the CloudFormation service
  2. Click Create Stack
  3. Under the Specify template section, provide the S3 URL for main.yml.
  4. Please provide the following:
    • Stack name
    • PublicSubnet
    • DefaultPublicSubnetCidr
    • KeyName
  5. Click Next 3 times.
    • You can leave the configurations under Configure stack options as default.
  6. Review the parameters under Step 2. Make sure all of the values provided are filled.
  7. Acknowledge the following capabilities. Click Create stack

Additional Note

Want to find out how to create the components and EC2 instances via the AWS Management Console? You can refer to this article instead!

Resources

Here are some resources you can refer to for more information. Happy reading!

Did you find this article valuable?

Support Bernice Choy by becoming a sponsor. Any amount is appreciated!

ย