In Part 3, we ended up with a bastion host and a private host. But we still have a file named vpc.yalm. And we are still going to add to that file, in theory. So, maybe now would be a good time to split our infrastructure in different files.
We are going to separate the content from the vpc.yaml file and use a master.yaml file to do so. The master.yaml file will link to the vpc.yalm file and to the rest of our infrastructure yaml files.
What we will need in the master.yaml file are all the parameters and associated metadata because this is going to be the file we upload to Cloudformation. The other files will be at the ready in an S3 bucket.
You have already used Cloudformation in Part 1, 2 and 3. But your IAM user will need S3 rights to create or at least upload and access files. If your IAM user has these permissions already, you are good to go. If not, you will have to ask your administrator for these permissions. If you have admin permissions already, you can add the necessary permissions to the IAM user you are using for this series or if it is your main IAM user, there is nothing to do. As service permissions can get tricky very quickly, in the discovery phase, it is best to not limit yourself too much with restrictive permissions. But, “with great power …”
As a side note, I suggest to create an IAM user with administrator permissions in a dedicated AWS throwaway account in your Organisation during the exploration phase. If this does not make any sense to you at this stage, it's absolutely fine. Forget it, and move on.
Let's create an S3 bucket. Give it a name, select the region you are working in and nothing more. Create it. Done.
We can add the VPC part in to the S3 bucket, we just have to remove the bastion and private host bits from the vpc.yaml file in the Metadata, Parameters and Resources sections. Once the file is uploaded, we need to copy the files URL.
We can now create a master.yaml file. In it, we will have parameters for all stacks we will refer to. For now, we will have the exact same Metadata, and Parameters sections as in the vpc.yaml file we just uploaded.
The Resources section will change. We will reference our S3 vpc.yaml via its S3 URL. We will as well set all parameters needed in the vpc stack.
Here is the Resources section. If you are unsure, everything is in the Github repository: check out tag part4.1.
[...] Resources: VPCStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: https://cfntutorialbob.s3-eu-west-1.amazonaws.com/vpc.yaml Parameters: PrivateSubnet1CIDR: !Ref PrivateSubnet1CIDR PrivateSubnet2CIDR: !Ref PrivateSubnet2CIDR PrivateSubnet3CIDR: !Ref PrivateSubnet3CIDR PublicSubnet1CIDR: !Ref PublicSubnet1CIDR PublicSubnet2CIDR: !Ref PublicSubnet2CIDR PublicSubnet3CIDR: !Ref PublicSubnet3CIDR VPCCIDR: !Ref VPCCIDR
We are using the
AWS::CloudFormation::Stack type and reference our vpc.yaml file via its URL.
In the AWS console, go to Cloudformation and upload the master.yaml file and execute the stack. You will notice, a nested stack will be created. You can now follow the vpc creation in the nested stack that gets a name like this: <the master stack name>-VPCStack-<Random differentiator>
It's time to create our bastion stack: bastion.yaml
We will need a VPC ID, a subnet ID, a key pair and an access CIDR.
The key pair and CIDR access, we will set in the master stack. But the VPC ID and subnet ID are defined in the VPC stack. We need a way to get the ID from what we create in the VPC stack. And there is a very simple one. Remember when we used Outputs to get the IP and DNS name from the bastion host? Well, we are going to do the same here. Update the vpc.yaml file and add the following Outputs to our VPC stack.
Upload the vpc.yaml file to your S3 bucket and update the master stack. Always update the master stack! Not the nested stack. This will ensure Cloudformation stays in sync with changes and rollbacks.
When you update, re-use the same master stack. We did not change it. Click the "Next" buttons up to "Update Stack" and click it.
The master stack updates, the VPC stack updates. Click on the VPC stack and go to the Outputs tab. There is the information we can refer to in the master stack.
We know part of the information for our bastion stack is in the VPCStack, in the Outputs, and we named them ProjectXDVPCID and ProjectXPublic1.
The key pair and ssh CIDR will be added as normal parameters. To avoid confusion, we name them exactly as they are named in the bastion.yaml file. Here is our Resources section for the nested bastion stack.
[...] BastionStack: Type: AWS::CloudFormation::Stack Properties: TemplateURL: https://cfntutorialbob.s3-eu-west-1.amazonaws.com/bastion.yaml Parameters: VPCID: !GetAtt VPCStack.Outputs.ProjectXDVPCID VPCSubnetID: !GetAtt VPCStack.Outputs.ProjectXPublic1 SSHRemoteAccessCIDR: !Ref SSHRemoteAccessCIDR BastionKeyPairName: !Ref BastionKeyPairName
Upload the bastion.yaml file to your S3 bucket and update the master stack. This time, our master stack has changed, so we need to re-upload our master.yaml file to Cloudformation.
In the Parameters, I chose my SSH CIDR (my outgoing IP address at work) and my key pair (cfntest) exactly like I did in Part 3. Next your way to the "Update Stack" button. In "Change set preview" you can see that the bastion stack is going to be added to our infrastructure.
. Next your way to the "Update Stack" button. In "Change set preview" you can see that the bastion stack is going to be added to our infrastructure.
Update the master stack.
Oh!Oh! I have a Rollback going on ... Let's keep it real. This is going to happen to you too 😉 And in my case, it says a made an error in the bastion stack where SubnetID does not have a value.
Allright, my master.yaml says
[...] VPCSubnetID: !GetAtt VPCStack.Outputs.ProjectXPublic1
My bastion.yaml file uses SubnetID, aaaha! I will change that in the bastion.yaml file so the propper variable is used: VPCSubnetID.
Same player, shoot again! And we have our new stack up. The bastion host is up and running.
Don't forget you can always refer to the Github repository: check out tag part4.2.
We can add a private host now for our app: app.yml.
We will need the VPC ID for the app and the Bastion security group ID for the app's security group. We will need a key pair (we will keep the key pair used for the bastion host) and a VPC subnet for the EC2 instance.
We know how to get the VPC ID and a subnet from the VPC stack. We get the key pair from the master Parameters in Cloudformation. We need to get the bastion security group from the bastion stack. So, we need to edit the Outputs sections.
[...] Outputs: BastionHostDNS: Description: Bastion host public DNS Value: !GetAtt BastionHost.PublicDnsName BastionPublicIPAddress: Description: Bastion host public IP Value: !GetAtt BastionHost.PublicIp BastionSG: Description: Bastion security group Value: !Ref BastionSecurityGroup
It is left for you as an exercice to do the app.yaml file. If you are in trouble along the way, you can check out tag part4.3 in the Github repository.
Upload the modified bastion.yaml file and update the master stack.
We will need the reference to the bastion security group in the Outputs of the bastion stack before we proceed with the app stack.
Add he new app.yaml file to your S3 bucket. Update the master stack on Cloudformation with the modified master.yaml file (now containing the resource for the app stack).
Let's test if we can log on to the bastion host and then to the private host.
$ ssh -A email@example.com The authenticity of host '126.96.36.199 (188.8.131.52)' can't be established. ECDSA key fingerprint is SHA256:fUqTEaCmihZAog/GGQ6XVV/yvhJrMbRCV1kGlxyMPOE. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '184.108.40.206' (ECDSA) to the list of known hosts. [centos@ip-10-0-1-228 ~]$ ssh firstname.lastname@example.org The authenticity of host '10.0.10.206 (10.0.10.206)' can't be established. ECDSA key fingerprint is SHA256:O0fHAm8GSBEEKdMLTIYEmDHO8X7RHW//4y4MrY3Vytw. ECDSA key fingerprint is MD5:74:a1:b7:55:99:28:ae:0b:77:09:a0:e1:b7:fb:62:4e. Are you sure you want to continue connecting (yes/no)? yes Warning: Permanently added '10.0.10.206' (ECDSA) to the list of known hosts. [centos@ip-10-0-10-206 ~]$
It works as planned.
We are now at the very same stage we left off in Part 3. But we have a much better layout for the components that are making up our infrastructure.
If you encounter errors you can't fix, you can always delete the master stack and start over: you can check out tags part4.1, part4.2 and part 4.3.
Join me in Part 5 where we will deploy an nginx into our app stack with a twist and load balance it.