Earlier, I posted an example CoudFormation template that creates a simple VPC and promised an update that would also create a a VPC with an EC2 Linux jump server. That template is below — and is, surprise!, in YAML.
While I am not a fan of the tooling AWS provides for CloudFormation, now that you can use its CloudFormation Designer with YAML, there’s no reason not to use YAML. YAML is so much easier to code and debug, though I wish my favorite editor (Visual Studio Code) had better support for YAML indentation. I’m not sure which is harder: debugging JSON with missing braces and/or brackets or YAML with messed up indentation. Wait a minute….YAML is definitely easier. 🙂 And this nifty little translation tool between JSON and YAML makes conversions easy.
I was reminded just today of how useful these skeleton, but runnable, templates can be. A client asked for a management VPC to be peered to a running production VPC. Using this template, I produced a VPC to the client’s specifications in about 10 minutes — far faster than you can do it by hand. And, everything is nicely labelled via tags and clear to anyone who looks at it. I did add the VPC peering connections by hand simply because that was faster and I wanted the client to be able to respond to his internal users before lunch. That’s the power of using CloudFormation!
Here are some notes on this template:
- It builds precisely the same VPC and subnets as the earlier post
- It has a table for AWS Linux AMIs only in the regions I use
- Be sure you have a valid EC2 key pair that you can specify when using the template — the name in the KeyPairName parameter is invalid.
I hope you find this as useful as I have. I have been gratified by the positive comments on previous CloudFormation sample posts and hope you’ll take a minute to comment, good or bad, on this example.
AWSTemplateFormatVersion: '2010-09-09'
Description: Creates a two-subnet VPC (public w/ NAT gateway and private) with a Linux
bastion instance in the public subnet (c) 2017 Air11 Technology LLC -- licensed
under the Apache OpenSource 2.0 license, https://opensource.org/licenses/Apache-2.0
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: VPC configuration parameters
Parameters:
- VPCCIDR
- PublicSubnetCIDR
- PrivateSubnetCIDR
- SSHLocation
- KeyPairName
ParameterLabels:
VPCCIDR:
default: Enter CIDR of new VPC
PublicSubnetCIDR:
default: Enter CIDR of the public subnet
PrivateSubnetCIDR:
default: Enter CIDR of the private subnet
SSHLocation:
default: Subnet allowed to ssh on TCP to public subnet
KeyPairName:
default: Key pair for bastion host
Parameters:
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]))$
ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
Default: 10.10.0.0/16
Description: CIDR block for entire VPC.
Type: String
PublicSubnetCIDR:
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]))$
ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
Default: 10.10.10.0/24
Description: CIDR block for the public subnet
Type: String
PrivateSubnetCIDR:
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]))$
ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28
Default: 10.10.20.0/24
Description: CIDR block for the private subnet
Type: String
SSHLocation:
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])(/([0-9]|[1-2][0-9]|3[0-2]))$
ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/0-28
Default: 0.0.0.0/0
Description: Network allowed to ssh to instances in public subnet.
Type: String
KeyPairName:
Description: Keypair for Linux bastion host
Type: AWS::EC2::KeyPair::KeyName
Default: your-keypair-name-here
Mappings:
RegionMap:
us-east-1:
AMI: ami-22ce4934
us-west-1:
AMI: ami-9e247efe
us-east-2:
AMI: ami-7bfcd81e
us-west-2:
AMI: ami-8ca83fec
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
EnableDnsSupport: 'true'
EnableDnsHostnames: 'true'
CidrBlock: !Ref 'VPCCIDR'
Tags:
- Key: Name
Value: !Sub 'VPC ${VPCCIDR}'
- Key: CloudFormationStack
Value: !Ref 'AWS::StackId'
InternetGateway:
Type: AWS::EC2::InternetGateway
Properties:
Tags:
- Key: Name
Value: IGW
- Key: CloudFormationStack
Value: !Ref 'AWS::StackId'
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref 'VPC'
CidrBlock: !Ref 'PublicSubnetCIDR'
Tags:
- Key: Name
Value: !Sub 'Public ${PublicSubnetCIDR}'
- Key: CloudFormationStack
Value: !Ref 'AWS::StackId'
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref 'VPC'
CidrBlock: !Ref 'PrivateSubnetCIDR'
Tags:
- Key: Name
Value: !Sub 'Private ${PrivateSubnetCIDR}'
- Key: CloudFormationStack
Value: !Ref 'AWS::StackId'
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref 'VPC'
InternetGatewayId: !Ref 'InternetGateway'
EIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
NAT:
DependsOn: AttachGateway
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt 'EIP.AllocationId'
SubnetId: !Ref 'PublicSubnet'
PublicSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref 'VPC'
Tags:
- Key: Name
Value: !Sub 'Public ${PublicSubnetCIDR}'
- Key: CloudFormationStack
Value: !Ref 'AWS::StackId'
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref 'PublicSubnetRouteTable'
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref 'InternetGateway'
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref 'PublicSubnet'
RouteTableId: !Ref 'PublicSubnetRouteTable'
PublicInstanceSG:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref 'VPC'
GroupDescription: Enable SSH access via port 22
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: !Ref 'SSHLocation'
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: !Sub 'PublicSG ${VPCCIDR}'
- Key: CloudFormationStack
Value: !Ref 'AWS::StackId'
PrivateSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref 'VPC'
Tags:
- Key: Name
Value: !Sub 'Private ${PrivateSubnetCIDR}'
- Key: CloudFormationStack
Value: !Ref 'AWS::StackId'
PrivateSubnetRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref 'PrivateSubnetRouteTable'
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref 'NAT'
PrivateSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref 'PrivateSubnet'
RouteTableId: !Ref 'PrivateSubnetRouteTable'
PrivateSubnetInstanceSG:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref 'VPC'
GroupDescription: Enable all TCP ports from instances in this VPC
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '1'
ToPort: '65535'
CidrIp: !Ref 'VPCCIDR'
Tags:
- Key: Name
Value: !Sub 'PrivateSG ${VPCCIDR}'
- Key: CloudFormationStack
Value: !Ref 'AWS::StackId'
LinuxBastionHost:
Type: AWS::EC2::Instance
DependsOn:
- AttachGateway
Properties:
KeyName: !Ref 'KeyPairName'
ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI]
InstanceType: t2.micro
NetworkInterfaces:
- AssociatePublicIpAddress: true
DeleteOnTermination: true
Description: ENI for bastion host
DeviceIndex: '0'
SubnetId: !Ref 'PublicSubnet'
GroupSet:
- !Ref 'PublicInstanceSG'
Tags:
- Key: Name
Value: !Sub 'Linux bastion ${VPCCIDR}'
- Key: CloudFormationStack
Value: !Ref 'AWS::StackId'
Outputs:
VPCId:
Description: VPCId of the newly created VPC
Value: !Ref 'VPC'
NatGateway:
Description: NAT gateway instance
Value: !Ref 'NAT'
EIPAddress:
Description: EIP allocated to NAT gateway
Value: !Ref 'EIP'
PublicSubnet:
Description: SubnetId of the public subnet
Value: !Ref 'PublicSubnet'
PublicSubnetRouteTable:
Description: Public route table
Value: !Ref 'PublicSubnetRouteTable'
PublicInstanceSG:
Description: SG for instances in public subnet
Value: !Ref 'PublicInstanceSG'
PrivateSubnet:
Description: SubnetId of the public subnet
Value: !Ref 'PrivateSubnet'
PrivateSubnetRouteTable:
Description: Private route table
Value: !Ref 'PrivateSubnetRouteTable'
PrivateSubnetInstanceSG:
Description: SG for instances in the private subnet
Value: !Ref 'PrivateSubnetInstanceSG'
LinuxBastionHost:
Description: Linux bastion instance
Value: !Ref 'LinuxBastionHost'
Leave a Reply