Cloud Security Skill-Building - Week 2
March 13, 2026 ·
What I learned this week
This week, I focused on building my first real network boundary in AWS by setting up a Virtual Private Cloud (VPC). I also got an opportunity to work alongside a team that manages a Salesforce cloud environment, which gave me additional exposure to how cloud security controls show up in real systems.
My main goal for this week was to build a secure AWS setup where an EC2 instance runs in a private subnet, can be managed without SSH, and can still access S3 privately — including downloading objects encrypted with server-side encryption using AWS Key Management Service (SSE-KMS).
VPC Basics
A VPC is basically my own isolated network inside AWS. The part that really clicked for me is that subnets are not “public” or “private” because of their name — they become public or private based on how they are routed.
A subnet is “public” if it has a route to the internet (through an Internet Gateway). A subnet is “private” if it does not have a route to the internet.
So the route table is what actually determines the security posture.
What I built
VPC + Subnets
I created a VPC:
10.0.0.0/16
Then I created two subnets:
- Public subnet:
10.0.1.0/24 - Private subnet:
10.0.2.0/24
I attached an Internet Gateway (IGW) to the VPC and created two route tables:
Public route table
10.0.0.0/16 → local0.0.0.0/0 → Internet Gateway
Private route table
10.0.0.0/16 → local
Then I associated:
- public subnet → public route table
- private subnet → private route table
This gave me a real “inside vs outside” boundary.
Private EC2 (No SSH, no inbound rules)
I launched an EC2 instance in the private subnet with:
- No public IP
- A security group with no inbound rules
- An IAM role (
LogReaderEC2Role) for temporary credentials
Instead of SSH, I used:
- AWS Systems Manager Session Manager
This was a big shift in how I think about “secure admin access” because the goal wasn’t just “lock down SSH,” it was remove the entire need for it.
Where things broke (and what it taught me)
In Week 1, my main issue was authorization:
- I had S3 permission but lacked KMS permission (
kms:Decrypt).
This week’s issues were different:
- I could have correct IAM permissions and still fail if there’s no network path to the service.
So this week was less about “am I allowed?” and more about “can I even reach it?”
Issue 1 — Session Manager wouldn’t connect
When I tried to connect via Session Manager, it didn’t work. The instance wasn’t showing properly in Systems Manager.
Root cause
My private subnet had:
- no NAT gateway
- no VPC endpoints
So the instance had no path to reach Systems Manager.
Fix
I created Interface VPC Endpoints for:
ssmssmmessagesec2messages
After that, Session Manager connected successfully without opening inbound ports.
Issue 2 — STS calls hung with no output
Next, I ran:
aws sts get-caller-identity
It just hung.
Root cause
The instance couldn’t reach AWS Security Token Service (STS), which is required for the role credentials flow.
Fix
I created an Interface Endpoint for:
com.amazonaws.us-west-2.sts
After that, aws sts get-caller-identity returned my account/role identity, confirming the instance role credentials were working.
Issue 3 — S3 commands hung
After that, commands like:
aws s3 ls
hung indefinitely.
Root cause
Private subnet had no route to S3. S3 commonly uses a Gateway VPC Endpoint, not an interface endpoint.
Fix
I created an S3 Gateway VPC Endpoint and associated it with the private route table.
After that, I could list the bucket contents privately:
aws s3 ls s3://david-secure-logs-2026
Issue 4 — My private subnet wasn’t actually private
During debugging, I discovered my private subnet had been associated with the public route table at some point, which meant it effectively had:
0.0.0.0/0 → Internet Gateway
Fix
I re-associated the subnet with the private route table so it was truly private again.
This reinforced a big lesson: route tables matter more than labels.
Issue 5 — KMS AccessDenied (again, but now it made more sense)
When I finally tried to download an encrypted object from S3:
aws s3 cp s3://david-secure-logs-2026/upload-text.txt .
I got:
AccessDenied(not authorized to performkms:Decrypt)
Root cause
Same concept as Week 1:
Encrypted S3 objects require two layers:
- S3 authorization (
s3:GetObject) - KMS authorization (
kms:Decrypt)
Fix
I added a least-privilege inline policy allowing:
kms:Decrypt
restricted to the specific KMS key.
After that, the object downloaded successfully.
Final small issue — OS permissions
At the very end I hit:
- “Permission denied”
because I was accidentally writing into a protected directory.
Fix was simple:
cd ~(or/tmp) and re-run the copy command.
Final architecture (in plain terms)
By the end of the lab, I had:
- an EC2 instance in a private subnet
- no public IP
- no inbound ports
- no SSH
- private access to AWS services using VPC endpoints
- the ability to download SSE-KMS encrypted objects from S3
Everything stayed inside AWS networking, which is the security win.
What I learned
This week taught me that cloud security isn’t just IAM permissions. It’s layers:
- Network path (can I reach the service?)
- Identity (do I have working credentials?)
- Authorization (do I have permission?)
- Encryption boundaries (do I also need KMS permission?)
- Host environment (am I writing files somewhere valid?)
It also taught me that architecture can reduce exposure without relying on “rules everywhere.” Subnet placement, route tables, and public IP assignment can prevent whole categories of risk before they even exist.
This is still an ongoing journey, and the more I build these environments from scratch, the more I’m understanding how cloud security becomes real through implementation, not just theory.