As part of the Well-Architected Framework from AWS, security is one of the main fundamental aspects in designing and delivering any AWS project. In this project we will demonstrate how to transform a vulnerable cloud environment into a secure and production grade architecture. After deploying the insecure baseline, the step-by-step re-design will follow the security-first design principles by involving the AWS services such as custom VPC, secured EC2, RDS, S3, CloudFront, and ALB (Application Load Balancer) with WAF protection enabled.
To start of with, here is the to-be architecture diagram for this project:

Below is the explanation of above architecture diagram:
- The user make the request to the application via HTTPS
- AWS WAF filters the requests for common threat
- ALB forwards cleaned and filtered request to EC2 instance
- EC2 runs the backend apps and gets data from RDS
- NAT gateway allows EC2 to access the internet for updates
- Bastion host allows a secure SSH connection to EC2
- Internet gateway connects public subnet with the internet
Step 1: Insecure Setup – Launch an EC2 Instance with Public Access
- Go to the EC2 Dashboard, click Launch Instance
- Name:
shoppingcart-app - AMI: Amazon Linux 2 or Amazon Linux 2023 (both supported)
- Instance type:
t2.micro(Free Tier eligible)
- Name:

- Key pair: Create and download a new key pair (.pem file) for SSH access
- Network settings:
- VPC: Use the default VPC
- Auto-assign public IP: Enable this option
- Security Group settings – Add these inbound rules:


Step 2: Insecure Setup – Install Node.js and Run the Application
We are simulating a web application deployment where the servers were setup without proper security
Connect to EC2 instance with the .pem key
ssh -i your-key.pem ec2-user@<your-ec2-public-ip>
and then install Node.js using nvm
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash
export NVM_DIR="$HOME/.nvm"
source "$NVM_DIR/nvm.sh"
nvm install 24
nvm use 24
Create the Shopping Cart application
mkdir shoppingcart && cd shoppingcart
mkdir public
cat <<EOF > public/index.html
<!DOCTYPE html>
<html>
<head><title>ShoppingCart</title></head>
<body><h1>Welcome to ShoppingCart</h1><p>This is the insecure Demo Page</p></body>
</html>
EOF
cat <<EOF > server.js
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer((req, res) => {
const filePath = path.join(__dirname, 'public', req.url === '/' ? 'index.html' : req.url);
fs.readFile(filePath, (err, content) => {
if (err) {
res.writeHead(404); res.end('Not Found');
} else {
res.writeHead(200); res.end(content);
}
});
});
server.listen(80);
EOF
Get the full path to Node.js and copy the output
[ec2-user@ip-172-31-18-36 shoppingcart]$ which node
~/.nvm/versions/node/v24.16.0/bin/node
Start the application
# Get the full path to Node.js
which node
# Copy the output path (e.g., /home/ec2-ser/.nvm/versions/node/v24.16.0/bin/node) Please adjust the node version on your own installation
# Start the server using the full path (& makes sure the task runs on background to keep terminal functional for operations)
sudo /home/ec2-user/.nvm/versions/node/v24.16.0/bin/node server.js &
Test the application by opening it using the EC2 instance public IP

Step 3: Insecure Setup – Deploy a Public RDS Instance
Again, we are simulating the most common miss-configuration in deploying database service which makes it publicly exposed to the public internet due to initially deployment for quick access.
- Go to Aurora and RDS Dashboard, click Create Database
- Choose Easy Create and change the value later after database launched
- Engine options:
- Creation method: Standard Create
- Engine: MySQL
- Template: Free Tier

- Settings:
- DB instance identifier:
shoppingcart-db - Master username:
admin - Set a simple password (for example,
Secret123!)
- DB instance identifier:

- Connectivity (Here is where the critical security issues are happening):
- VPC: Default VPC
- Public access: YES (Major vulnerability – never do this in production)
- VPC security group: Create new or use existing

Security Group Configuration on EC2:
- Edit the security group to add:
- Inbound rule: MySQL/Aurora (port 3306) from
0.0.0.0/0

Step 4: Insecure Setup – Create Public S3 Bucket
In here, we are demonstrating how S3 bucket miss-configuration can lead to accidentally sensitive data to be exposed.
- Go to S3 Dashboard, click Create Bucket
- Name the bucket:
shoppingcart-insecure-demo. (Ensure the name has to be globally unique) - Choose the same region as your EC2 instance.
- Name the bucket:

- Under Block Public Access settings:
- Uncheck Block all public access
- Acknowledge the warning prompt

Click Create Bucket
- Upload a Sample File
- Click into your newly created bucket → Upload tab
- Upload a sample file, such as
shoppingcart-logo.png, and click Upload

- Apply Dangerous Bucket Policy
- Go to the Permissions tab of the bucket.
- Scroll to Bucket Policy → click Edit
- Paste the following policy and save (Modify the resource value accordingly to your bucket name):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPublicReadAccessToObjects",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shoppingcart-insecure-demo/*"
}
]
}

This allows all users (even unauthenticated) to read any object in this bucket.
- Verify Public Access
- Go back to the uploaded object (
shoppingcart-logo.png) - Copy the Object URL (visible at the top)
- Go back to the uploaded object (

- Open it in an incognito/private browser window
- The file should be accessible without any authentication via below URL:
https://shoppingcart-insecure-demo.s3.us-east-1.amazonaws.com/shoppingcart-logo.png
Summary
At this point, we have successfully deployed a highly insecure cloud environment that mimics common real-world issues and vulnerabilities as below list:
- Network Security Issues
- No network segmentation between tiers
- All resources deployed in the public subnets
- Overly permissive security groups
- Data Protection Failures
- Database exposed to public internet
- No encryption for data at rest or in transit
- Publicly accessible file storage
- Access Control Problems
- SSH access is allowed from anywhere
- Database is accessible globally
- No IAM role-based access controls
- Monitoring and Compliance Gaps
- No logging or monitoring configured
- No threat detection enabled
- No compliance tracking
In part 2, we’ll systematically address each of these vulnerabilities using AWS security best practices, transforming this insecure setup into a production-ready with the proper secure architecture best practices.