DEV Community

Cover image for Architecting Secure S3 Access for EC2 Using IAM Roles and Temporary Credentials
Naomi Ansah
Naomi Ansah

Posted on

Architecting Secure S3 Access for EC2 Using IAM Roles and Temporary Credentials

One thing I’ve started appreciating more while learning AWS is that good cloud architecture is not just about making systems work, it’s also about controlling how systems access each other securely.

Recently, I built a small Terraform project to better understand how companies allow EC2 instances to access Amazon S3 without storing AWS credentials directly on servers.

At first, the setup looked straightforward. Launch an EC2 instance, attach an IAM Role, and allow access to S3. But after testing the permissions directly from inside the EC2 instance itself, a lot of IAM concepts finally started making practical sense to me.

Before moving into IAM Roles for EC2, I first tested AWS permissions using IAM Users and Groups. I created a Developers group, attached AmazonS3ReadOnlyAccess at the group level, then added an IAM user into the group to verify inherited permissions.

The IAM user could successfully list S3 buckets, but uploads were denied by policy. That small test helped reinforce an important AWS security principle early for me: permissions are evaluated based on what actions are explicitly allowed.

That same idea became much more interesting later when I tested IAM Roles directly from inside an EC2 instance.

The business scenario behind this architecture is actually very common. Imagine a company storing application assets, deployment packages, reports, or configuration files inside Amazon S3. Their EC2 instances may need access to retrieve those files during deployments or application runtime. One bad approach would be storing long-term AWS access keys directly on the EC2 server. That creates unnecessary security risks because credentials can leak, be reused insecurely, or accidentally end up inside GitHub repositories.

Instead, AWS allows EC2 instances to inherit temporary credentials securely using IAM Roles. The EC2 instance receives temporary credentials automatically through AWS Security Token Service (STS), which removes the need for hardcoded access keys completely.

*Architecture diagram illustrating secure Amazon S3 access from an EC2 instance using an IAM role with temporary credentials. The EC2 instance (Amazon Linux 2023) assumes the  raw `S3_role` endraw  IAM role, which has the  raw `AmazonS3ReadOnlyAccess` endraw  managed policy attached. The diagram highlights that  raw `s3:ListBucket` endraw  and  raw `s3:GetObject` endraw  actions are allowed, while  raw `s3:PutObject` endraw  is denied, resulting in an  raw `AccessDenied` endraw  error when attempting file uploads to the  raw `naomi-portfolio-site` endraw  S3 bucket.*

To better understand the workflow practically, I used Terraform to provision the entire setup. The project created:

an IAM Role trusted by EC2
an IAM policy attachment
an IAM Instance Profile
a security group for SSH access
an Amazon Linux 2023 EC2 instance
a dynamic AMI lookup using a Terraform data source

One thing I appreciated during this project was seeing how Terraform mirrors the same workflow engineers normally perform manually in the AWS Console. Instead of clicking through IAM, EC2, role attachments, and security groups one after another, the entire architecture became reusable and declarative.

The IAM Role itself trusted the EC2 service using this trust relationship:

Principal = {
Service = "ec2.amazonaws.com"
}

That trust relationship is what allows the EC2 service to assume the role securely. I then attached the AWS managed policy:

AmazonS3ReadOnlyAccess

At this point, the EC2 instance should be able to list buckets and read objects from S3, but it should not be able to upload or modify objects. That distinction became very important later during testing.

Another concept that became much clearer to me during this project was the role of the IAM Instance Profile. Initially, it looked like the EC2 instance simply attaches directly to the IAM Role, but that is not actually how AWS handles it internally.

The relationship is actually:

EC2 Instance → Instance Profile → IAM Role

Without the instance profile, the EC2 instance cannot inherit the role permissions properly.

After deploying the infrastructure, I ran into an SSH issue that ended up teaching me another useful lesson. At some point, SSH suddenly stopped working and the connection kept timing out. Initially I thought the EC2 instance itself had failed, but the actual issue turned out to be my own public IP changing after switching Wi-Fi networks.

Because the security group allowed SSH access only from my IP/32, the old IP no longer matched the inbound rule. Updating the security group fixed the issue immediately. Small troubleshooting moment, but it reinforced how tightly security groups depend on source IP ranges.

Once connected to the EC2 instance through SSH, I started testing the IAM Role directly from inside the server.

First, I ran:

aws s3 ls

The S3 bucket appeared successfully.

*Screenshot showing successful execution of the  raw `aws s3 ls` endraw  command from an EC2 instance using an IAM role. The terminal output confirms that temporary credentials attached through the IAM role allowed secure access to the S3 bucket without hardcoding AWS access keys.*

That immediately confirmed a few important things:

the EC2 instance successfully inherited the IAM Role
temporary credentials were being provided automatically
no access keys were stored on the server

At this point, the architecture was already working correctly.

But the most educational part came next.

I intentionally wanted to test what the EC2 instance could NOT do.

So I created a small test file:

echo "testing upload" > test.txt

Then attempted to upload it into the S3 bucket:

aws s3 cp test.txt s3://naomi-portfolio-site/

AWS immediately responded with:

AccessDenied

*Screenshot showing an  raw `aws s3 cp` endraw  upload attempt from an EC2 instance to an Amazon S3 bucket failing with an  raw `AccessDenied` endraw  error. This demonstrates how IAM permissions control access to AWS resources and highlights the importance of attaching the correct S3 policies to an EC2 IAM role before performing upload operations.*

That single error taught me more about IAM Roles practically than multiple diagrams ever had.

The EC2 instance was authenticated successfully through the IAM Role, but authorization was still restricted by the attached IAM policy. The role worked correctly. The policy enforcement also worked correctly. The upload failed specifically because the policy did not allow:

s3:PutObject

Seeing that distinction happen in real time made the least-privilege model feel much more practical to me.

Before this project, IAM Roles mostly made sense conceptually. But testing permissions directly from inside the EC2 instance made the workflow feel real in a way diagrams alone never fully captured.

This project reinforced several AWS concepts for me, especially IAM Roles for EC2, temporary credentials using STS, least-privilege access, security group source IP restrictions, Terraform resource relationships, and how AWS evaluates IAM permissions in practice.

More importantly, it reminded me that secure cloud architecture is not only about granting access, it is also about intentionally restricting actions that should not happen.

Sometimes the best way to understand AWS security properly is to intentionally test the boundaries of what should fail.

Top comments (0)