Cloud Security Skill-Building - Final Reflection
June 22, 2026
What I learned
I began this cloud-security learning project to move beyond theoretical cybersecurity concepts and build practical experience with cloud infrastructure, identity, encryption, monitoring, detection, and incident response.
I chose Amazon Web Services as the platform because I wanted to learn cloud-security concepts through a real environment rather than only memorizing individual services.
The project began with IAM and eventually developed into a complete security-engineering lifecycle:
Identity and authorization
→ networking
→ encryption
→ visibility
→ detection
→ Infrastructure as Code
→ incident containment
→ teardown
IAM, S3, and KMS
The first phase focused on Identity and Access Management.
I practiced:
- IAM users and roles
- temporary credentials
- policy structure
- resource scoping
- conditions
- implicit deny
- explicit deny
- least privilege
I wrote an IAM policy that combined:
s3:ListBuckets3:GetObject- prefix restrictions
- KMS decryption
- secure-transport enforcement
- explicit denial of protected paths
I also created an S3 bucket encrypted with a customer-managed KMS key.
Where things first broke
My EC2 role had S3 read access, but an encrypted object download returned:
AccessDenied
Root cause
The role had:
s3:GetObject
but did not have:
kms:Decrypt
The object was stored in S3, but the data could not be decrypted without separate KMS authorization.
Fix
I added a least-privilege KMS permission scoped to:
- the specific KMS key
- the S3 service
- the relevant encryption context
After that, the object downloaded successfully.
This established one of the main lessons of the project:
Access to an encrypted cloud resource can require authorization from multiple services.
Building the network boundary
The next phase focused on AWS networking.
I created:
- a VPC using
10.0.0.0/16 - a public subnet using
10.0.1.0/24 - a private subnet using
10.0.2.0/24 - an Internet Gateway
- public and private route tables
- a private EC2 instance
- security groups with no inbound rules
The private instance had:
- no public IP address
- no SSH access
- no open inbound ports
- an IAM role using temporary credentials
Instead of securing SSH, I removed the need for SSH and used AWS Systems Manager Session Manager.
Where networking broke
The private EC2 instance could not initially connect to Systems Manager.
Root cause
The private subnet had:
- no NAT Gateway
- no path to Systems Manager
- no VPC endpoints
Fix
I created Interface VPC Endpoints for:
- Systems Manager
- SSM Messages
- EC2 Messages
- Security Token Service
I also created an S3 Gateway Endpoint and associated it with the private route table.
This allowed the EC2 instance to communicate with the required AWS services without receiving a public IP address.
Route tables determine exposure
During troubleshooting, I discovered that the private subnet had been associated with the public route table.
That meant the subnet had a route through the Internet Gateway even though it was labeled “private.”
I corrected the route-table association.
This reinforced that:
A subnet is not public or private because of its name. Its routes determine its effective behavior.
A layered troubleshooting model
The IAM and networking exercises created a reusable troubleshooting process:
Network path
→ identity
→ authorization
→ encryption
→ operating-system behavior
A cloud request can fail because:
- the workload cannot reach the service
- credentials are unavailable
- IAM does not authorize the action
- KMS does not authorize the cryptographic operation
- the host cannot write to the requested location
Separating these layers helped me avoid treating every AWS problem as an IAM problem.
Logging and visibility
After building the environment, I focused on recording activity.
I configured:
- CloudTrail management events
- CloudTrail S3 Data Events
- VPC Flow Logs
- CloudWatch Logs
- CloudWatch Logs Insights
- metric filters
- custom metrics
- alarms
- SNS notifications
One important discovery was that enabling CloudTrail did not automatically provide visibility into every action.
Management events showed administrative activity, but S3 object access required Data Events.
After enabling S3 Data Events, I could identify:
- the IAM role
- the role session
- the action
- the bucket
- the object key
- the source address
- the user agent
- the event time
Building exfiltration detections
I created two detections for S3 GetObject behavior.
Burst detection
The first alarm detected more than five object reads within one minute.
It was designed to recognize:
- rapid retrieval
- noisy behavior
- obvious bulk access
Sustained detection
The second alarm evaluated elevated object access across consecutive five-minute periods.
It was designed to recognize:
- slower retrieval
- prolonged collection
- attempts to spread activity across time
The tests showed that both alarms could trigger during the same activity.
A sustained test could still exceed the burst threshold during an individual minute.
This taught me that detection rules must be tuned using observed behavior rather than relying only on the names assigned to them.
Identity-aware detection
The investigation evolved from asking:
How many GetObject events occurred?
to asking:
Which identity performed the actions, and was that behavior normal for the role?
A log-reading role may be authorized to retrieve objects, but repeated bulk retrieval can still be suspicious if it does not match the role’s intended purpose.
The detection logic began combining:
- identity
- action
- volume
- time window
- source
- expected behavior
This made the alert more meaningful than a simple event count.
IAM hardening
I tested restrictions based on:
- S3 object prefixes
- source IP addresses
- explicit deny logic
The role was restricted to:
allowed/*
Attempts to read other object paths were denied.
I also tested an aws:SourceIp condition.
At first, the condition appeared ineffective.
Root cause
Another broader S3 policy still authorized the request.
The conditional allow did not match, but the broader allow remained applicable.
Fix
I added an explicit deny to enforce the boundary.
Later, I simplified the policy by removing the broad permission and granting only the exact access required.
This reinforced that IAM policies must be evaluated together rather than one statement at a time.
Infrastructure as Code
I then rebuilt the environment using Terraform.
I practiced:
- provider configuration
- resource blocks
- local Terraform names
- variables
- outputs
- dependencies
- state
- formatting
- validation
- planning
- applying
- destruction
The project was divided into:
main.tfvariables.tfoutputs.tfdetection.tfcapstone.tf
Terraform managed:
- S3 configuration
- versioning
- encryption
- public-access blocking
- lifecycle policies
- bucket policies
- IAM roles
- IAM policies
- CloudTrail
- CloudWatch Logs
- metric filters
- alarms
The workflow became:
Write
→ format
→ validate
→ plan
→ review
→ apply
→ test
Detection-as-Code
The manually created S3 detection pipeline was rebuilt through Terraform.
I generated controlled S3 reads and confirmed:
- the event was recorded
- the event reached CloudWatch Logs
- the metric filter matched it
- the custom metric was created
- the alarm entered the expected state
This demonstrated that the security controls could be recreated consistently without depending on undocumented console steps.
Simulated compromise and containment
For the final capstone, I created a narrowly scoped S3 reader role.
I assumed the role through STS and used its temporary credentials to generate repeated object reads.
An identity-specific alarm detected the elevated activity.
I then attached an emergency explicit-deny policy and retried the request with the same temporary credentials.
The request was denied.
The completed response flow was:
Authorized role
→ abnormal behavior
→ CloudTrail evidence
→ identity-specific alert
→ investigation
→ explicit-deny containment
→ access blocked
This connected access-control engineering, detection engineering, and incident response.
Cost and operational awareness
The project also demonstrated that secure architecture can introduce cost.
The main charges came from:
- Interface VPC Endpoints
- EC2 instances
- EBS volumes
- public IPv4 usage
- CloudTrail Data Events
- CloudWatch logging
- the customer-managed KMS key
I avoided a NAT Gateway, but the collection of Interface Endpoints still generated hourly costs.
This reinforced that a cloud-security design must consider:
- security
- usability
- availability
- operational complexity
- cost
Teardown and final verification
Terraform destroyed:
29 resources
However, manually created resources remained outside Terraform state.
I audited and removed:
- CloudTrail trails
- CloudWatch alarms
- log groups
- SNS topics
- IAM policies
- IAM roles
- instance profiles
- EC2 instances
- EBS volumes
- VPC endpoints
- VPC Flow Logs
- security groups
- route tables
- subnets
- the Internet Gateway
- the non-default VPC
- S3 buckets and object versions
- the customer-managed KMS key
- the Terraform IAM user
- the long-lived access key
- local AWS credential entries
The final verification showed no remaining lab infrastructure.
The KMS key entered PendingDeletion, which was the expected state during its deletion waiting period.
Final architecture and workflow
The complete project followed this lifecycle:
Design
→ deploy
→ misconfigure
→ troubleshoot
→ monitor
→ detect
→ contain
→ rebuild as code
→ destroy
→ audit
→ verify
The most important outcome was understanding how these layers interact:
Networking
+ identity
+ authorization
+ encryption
+ logging
+ detection
+ response
+ automation
+ operations
What I learned
The strongest lessons often came from failures:
- having S3 access but lacking KMS authorization
- having IAM permission but no network path
- configuring an alarm that had not received telemetry
- writing a conditional allow that was bypassed by another policy
- destroying Terraform resources while manual resources remained
- trying to delete a VPC before removing all its dependencies
Each failure required me to identify the responsible layer, test my assumptions, and correct the design.
This cloud-security foundation phase is now complete.
The project gave me practical experience in:
- cloud architecture
- IAM engineering
- encrypted storage
- logging and detection
- behavioral investigation
- Infrastructure as Code
- incident containment
- cost management
- resource teardown
- credential lifecycle management
The environment itself was intentionally deleted, but the Terraform source, documentation, debugging process, and validated scenarios remain evidence of the work.
My next phase will broaden beyond AWS and focus more heavily on generalized security engineering, including:
- Windows and Linux telemetry
- endpoint and host investigation
- network-security analysis
- Sigma detection rules
- vulnerability and configuration management
- security automation
- repeatable incident-response workflows
This cloud project will remain one specialization within that broader security-engineering direction.