Module 8: Case Study - 1
Problem Statement: You work for XYZ Corporation. Your corporation wants to launch a new web-based application. The development team has prepared the code but it is not tested yet. The development team needs the system admins to build a web server to test the code but the system admins are not available.
Tasks To Be Performed:
- Web tier: Launch an instance in a public subnet and that instance should allow HTTP and SSH from the internet.
- Application tier: Launch an instance in a private subnet of the web tier and it should allow only SSH from the public subnet of Web Tier-3.
- DB tier: Launch an RDS MYSQL instance in a private subnet and it should allow connection on port 3306 only from the private subnet of Application Tier-4.
- Setup a Route 53 hosted zone and direct traffic to the EC2 instance.
You have been also asked to propose a solution so that:
- Development team can test their code without having to involve the system admins and can invest their time in testing the code rather than provisioning, configuring and updating the resources needed to test the code.
- Make sure when the development team deletes the stack, RDS DB instances should not be deleted.
Template 1: VPC Configuration
This template will set up a VPC, subnets, an internet gateway, and all the foundational components:
AWSTemplateFormatVersion: '2010-09-09'
Description: VPC and foundational network resources
Resources:
# VPC
MyVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
EnableDnsSupport: 'true'
EnableDnsHostnames: 'true'
Tags:
- Key: Name
Value: MyVPC
# Public Subnet
PublicSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVPC
CidrBlock: 10.0.1.0/24
MapPublicIpOnLaunch: 'true'
AvailabilityZone: !Select [ 0, !GetAZs '' ] # Choose the first AZ in the region
# Private Subnet
PrivateSubnet:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVPC
CidrBlock: 10.0.2.0/24
AvailabilityZone: !Select [ 1, !GetAZs '' ] # Choose the second AZ in the region
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref MyVPC
CidrBlock: 10.0.3.0/24
AvailabilityZone: !Select [ 2, !GetAZs '' ] # Choose the third AZ in the region
# Internet Gateway
InternetGateway:
Type: AWS::EC2::InternetGateway
AttachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref MyVPC
InternetGatewayId: !Ref InternetGateway
# Route table for the public subnet
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref MyVPC
PublicRoute:
Type: AWS::EC2::Route
DependsOn: AttachGateway
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet
RouteTableId: !Ref PublicRouteTable
# NEW ADDITION: Elastic IP for NAT Gateway
NatEIP:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
# NEW ADDITION: NAT Gateway in the public subnet
NatGateway:
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt NatEIP.AllocationId
SubnetId: !Ref PublicSubnet
# NEW ADDITION: Update the private subnet's route table to route traffic through the NAT Gateway
PrivateRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref MyVPC
PrivateSubnetRouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet
RouteTableId: !Ref PrivateRouteTable
PrivateRoute:
Type: AWS::EC2::Route
DependsOn: NatGateway
Properties:
RouteTableId: !Ref PrivateRouteTable
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway
Outputs:
VPC:
Description: A reference to the created VPC
Value: !Ref MyVPC
PublicSubnet:
Description: A reference to the public subnet
Value: !Ref PublicSubnet
PrivateSubnet:
Description: A reference to the private subnet
Value: !Ref PrivateSubnet
PrivateSubnet2:
Description: A reference to the private2 subnet
Value: !Ref PrivateSubnet2
This template establishes a VPC with two distinct subnets:
- A Public Subnet for the web tier, granting direct internet access via an Internet Gateway.
- A Private Subnet for the app tier, which utilizes a NAT Gateway in the public subnet to securely update instances and access the internet.
The infrastructure ensures that the app tier remains isolated while the web tier can be directly accessed from the internet.
Template 2: Application Infrastructure
This template assumes the VPC and foundational resources are already set up:
AWSTemplateFormatVersion: '2010-09-09'
Description: Web, App, and DB tiers with Route53 setup
Parameters:
VPCId:
Description: The VPC ID
Type: AWS::EC2::VPC::Id
PublicSubnetId:
Description: The public subnet ID
Type: AWS::EC2::Subnet::Id
PrivateSubnetId:
Description: The private subnet ID
Type: AWS::EC2::Subnet::Id
PrivateSubnetId2:
Description: The private subnet ID 2
Type: AWS::EC2::Subnet::Id
MasterUsername:
Description: RDS Master Username
Type: String
Default: admin
MasterUserPassword:
Description: RDS Master Password
Type: String
Default: Passw0rd!
NoEcho: true
DomainName:
Description: The domain name for the Route53 hosted zone (e.g., example.com.)
Type: String
Default: temp.hectorproko.com
ImageId:
Description: The AMI ID
Type: String
Default: ami-053b0d53c279acc90 #redhat ami-067d1e60475437da2
KeyName:
Description: SSH Key Name
Type: String
Default: daro.io
Resources:
# Web Tier
WebTierInstance:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: !Ref ImageId
KeyName: !Ref KeyName
SubnetId: !Ref PublicSubnetId
SecurityGroupIds:
- !Ref WebTierSecurityGroup
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
sudo apt-get update -y
sudo apt-get install apache2 -y
WebTierSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPCId
GroupDescription: "Security group for web tier"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '80'
ToPort: '80'
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
CidrIp: 0.0.0.0/0
# Application Tier
AppTierInstance:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: !Ref ImageId
KeyName: !Ref KeyName
SubnetId: !Ref PrivateSubnetId
SecurityGroupIds:
- !Ref AppTierSecurityGroup
UserData:
Fn::Base64: !Sub |
#!/bin/bash -xe
sudo apt-get update -y
sudo apt-get install mysql-server -y
AppTierSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPCId
GroupDescription: "Security group for app tier"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '22'
ToPort: '22'
SourceSecurityGroupId: !Ref WebTierSecurityGroup
# RDS MySQL (simplified)
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupDescription: "Subnet group for RDS"
SubnetIds:
- !Ref PrivateSubnetId
- !Ref PrivateSubnetId2
DBInstance:
Type: AWS::RDS::DBInstance
DeletionPolicy: Retain
Properties:
AllocatedStorage: '5'
DBInstanceClass: db.t2.micro
Engine: MySQL
MasterUsername: !Ref MasterUsername
MasterUserPassword: !Ref MasterUserPassword
VPCSecurityGroups:
- !Ref DBSecurityGroup
DBSubnetGroupName: !Ref DBSubnetGroup
DBSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref VPCId
GroupDescription: "Security group for RDS"
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: '3306'
ToPort: '3306'
SourceSecurityGroupId: !Ref AppTierSecurityGroup
# Route53
MyHostedZone:
Type: "AWS::Route53::HostedZone"
Properties:
Name: !Ref DomainName
HostedZoneConfig:
Comment: "Hosted zone for the web application"
WebDNSRecord:
Type: AWS::Route53::RecordSet
Properties:
HostedZoneId: !Ref MyHostedZone
Name: !Sub "${DomainName}."
Type: A
TTL: '300'
ResourceRecords:
- !GetAtt [WebTierInstance, PublicIp]
This CloudFormation template sets up a multi-tier architecture with clear layers of security and accessibility for a web application. The architecture comprises three main components: a web tier, an application tier, and a database tier.
The Web Tier creates an EC2 instance within a specified public subnet. This instance is equipped with a security group that permits incoming HTTP traffic, making it the public-facing component of the architecture. As part of its initialization, this instance installs the Apache2 server, allowing it to host a webpage.
The Application Tier establishes another EC2 instance, but this time in a private subnet, reinforcing its isolated nature. It’s set up to install a MySQL server upon initialization. The security group associated with this tier only allows SSH traffic, and uniquely, only from the web tier instance. This design ensures that direct external access is restricted, providing an additional layer of security.
Lastly, the Database Tier introduces an RDS instance configured for MySQL. It resides within a database subnet group spanning two private subnets. The security group for this RDS instance is designed to only accept connections on the MySQL port from the application tier, thereby ensuring that only the app tier can communicate with the database directly. Importantly, a safeguard has been put in place to ensure that even if the CloudFormation stack is deleted, the RDS DB instance will not be deleted, providing data resilience.
To complete the setup, a Route53 hosted zone and DNS record are created to associate the domain name with the public IP address of the web tier instance, ensuring users can access the hosted page via a friendly domain name.
By structuring the infrastructure in this way, the template promotes a layered security model, ensuring each component is shielded appropriately based on its function and exposure.
Verifying Web Page Accessibility
The hosted zone in Route 53 has been correctly configured with a record set pointing to the WebTier
instance - 54.242.228.183
.
When accessing the public IP address 54.242.228.183
directly, the default Apache2 welcome page from an Ubuntu server is displayed, confirming the web server’s correct operation.
After updating the domain’s nameservers in the registrar settings to match those provided by AWS Route 53, navigating to temp.hectorproko.com
successfully resolves to the Apache2 default page. This confirms that the domain is correctly pointing to the intended web server.
Success
Testing RDS Connection
From the Web Tier instance with IP ending in .247
, I initiated an SSH connection to the App Instance:
Upon establishing a successful connection to the App Instance ending in .67
, I then connected to the RDS instance using its provided endpoint:
Success
The MySQL prompt was displayed, confirming a successful connection to the RDS instance and indicating that all components are functioning as expected.