AWS and CloudFormation
I have recently been learning Terraform in order to deploy code into AWS, with and without pipelines for CI/CD. I was asked to create an entire infrastructure with CloudFormation which i had never used before. I have since been able to create and deploy multiuple stacks using CodePipeline to ensure it gets updated after any change to the CodeCommit repo.
Main CloudFormation Stack
Once a main CloudFormation stack has been defined, you can then start pointing each stack to the individual template files.
AWSTemplateFormatVersion: '2010-09-09'
Description: Master stack which creates all required nested stacks
Parameters:
vpccidr:
Description: "The VPC CIDR address range for the VPC to be deployed."
Type: String
MinLength: '9'
MaxLength: '18'
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x."
publicsubnet1:
Description: "The CIDR address range for public subnet 1."
Type: String
MinLength: '9'
MaxLength: '18'
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x."
publicsubnet2:
Description: "The CIDR address range for public subnet 2."
Type: String
MinLength: '9'
MaxLength: '18'
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x."
privatesubnet1:
Description: "The CIDR address range for private subnet 1."
Type: String
MinLength: '9'
MaxLength: '18'
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x."
privatesubnet2:
Description: "The CIDR address range for private subnet 2."
Type: String
MinLength: '9'
MaxLength: '18'
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x."
Resources:
VPCStack:
Type: AWS::CloudFormation::Stack
Properties:
TemplateURL: "https://S3_Bucket_Location/nwsis/vpc-stack.yml"
Parameters:
vpccidr: !Ref vpccidr
publicsubnet1: !Ref publicsubnet1
publicsubnet2: !Ref publicsubnet2
privatesubnet1: !Ref privatesubnet1
privatesubnet2: !Ref privatesubnet2
Tags:
- Key: Owner
Value: Owner_Name
- Key: Environment
Value: !Ref environment
- Key: Criticality
Value: !Ref criticality
- Key: Application
Value: Application Name
This main yaml file will then point to the vpc-stack.yml file in order to create the VPC infrastructure. The Ref function calls the value from the parameters sepcified above. The parameters in turn get the values from a JSON encoded file that holds the values that you want to create; including CIDR ranges for the subnets.
Stack Template File
AWSTemplateFormatVersion: '2010-09-09'
Description: "Creation of VPC and SubNets"
Parameters:
vpccidr:
Description: "The VPC CIDR address range for the VPC to be deployed."
Type: String
MinLength: '9'
MaxLength: '18'
AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x."
publicsubnet1:
Description: "The CIDR address range for public subnet 1."
Type: String
MinLength: '9'
MaxLength: '18'
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x."
publicsubnet2:
Description: "The CIDR address range for public subnet 2."
Type: String
MinLength: '9'
MaxLength: '18'
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x."
privatesubnet1:
Description: "The CIDR address range for private subnet 1."
Type: String
MinLength: '9'
MaxLength: '18'
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x."
privatesubnet2:
Description: "The CIDR address range for private subnet 2."
Type: String
MinLength: '9'
MaxLength: '18'
AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
ConstraintDescription: "Must be a valid IP CIDR range of the form x.x.x.x/x."
Resources:
S3Bucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: 'aws:kms'
KMSMasterKeyID: !Ref EncryptionKey
BucketKeyEnabled: true
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock:
Ref: vpccidr
EnableDnsHostnames: true
EnableDnsSupport: true
Tags:
- Key: Name
Value: VPC-NAME
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock:
Ref: publicsubnet1
MapPublicIpOnLaunch: false
VpcId:
Ref: VPC
Tags:
- Key: "Name"
Value: PublicSubnet1
AvailabilityZone: !Sub ${AWS::Region}a
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
CidrBlock:
Ref: publicsubnet2
MapPublicIpOnLaunch: false
VpcId:
Ref: VPC
Tags:
- Key: "Name"
Value: PublicSubnet2
AvailabilityZone: !Sub ${AWS::Region}b
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
CidrBlock:
Ref: privatesubnet1
MapPublicIpOnLaunch: false
VpcId:
Ref: VPC
Tags:
- Key: "Name"
Value: PrivateSubnet1
AvailabilityZone: !Sub ${AWS::Region}a
PrivateSubnet2:
Type: "AWS::EC2::Subnet"
Properties:
CidrBlock:
Ref: privatesubnet2
MapPublicIpOnLaunch: false
VpcId:
Ref: "VPC"
Tags:
- Key: "Name"
Value: PrivateSubnet2
AvailabilityZone: !Sub ${AWS::Region}b
EncryptionKey:
Type: AWS::KMS::Key
Properties:
Description: 'This is the key used to secure resources in this account'
EnableKeyRotation: True
KeyPolicy:
Version: '2012-10-17'
Statement:
- Sid: Allow root access
Effect: 'Allow'
Principal:
AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
Action:
- 'kms:*'
Resource: '*'
- Sid: Allow use of the key by this account
Effect: 'Allow'
Principal:
AWS: '*'
Action:
- 'kms:DescribeKey'
- 'kms:Encrypt'
- 'kms:Decrypt'
- 'kms:ReEncrypt*'
- 'kms:GenerateDataKey'
- 'kms:GenerateDataKeyWithoutPlaintext'
- 'kms:CreateGrant'
- 'kms:RevokeGrant'
Resource: '*'
Condition:
StringEquals:
kms:CallerAccount: !Ref 'AWS::AccountId'
EncryptionKeyAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: alias/encryptionKey
TargetKeyId: !Ref EncryptionKey
Outputs:
S3BucketName:
Description: "Name of the S3 bucket"
Value: !Ref S3Bucket
vpc:
Description: "VPC ID"
Value: !Ref VPC
PrivateSubnet1:
Description: "Subnet ID of private subnet in AZ1"
Value: !Ref PrivateSubnet1
PrivateSubnet2:
Description: "Subnet ID of private subnet in AZ2"
Value: !Ref PrivateSubnet2
PublicSubnet1:
Description: "Subnet ID of public subnet in AZ1"
Value: !Ref PublicSubnet1
PublicSubnet2:
Description: "Subnet ID of public subnet in AZ2"
Value: !Ref PublicSubnet2
Once the files are created you can then push to your repo. As long as your pipeline is pointing at your repo and configured to the correct branch, it should start deploying the cloud formation scripts. The error messages are very good and you can add as many nested stacks as you need to deploy all your environment.
git add .
git commit -m "Created the VPC Stack"
git push