IAM Roles for EC2: Grant S3 Read-Only Access (Hands-On Lab 0.2)


TL;DR — Quick Reference

  • Create IAM role → attach AmazonS3ReadOnlyAccess policy
  • Set trusted entity = EC2 (this generates the trust policy)
  • Attach role to instance (automatically creates instance profile)
  • Verify role assumption: aws sts get-caller-identity
  • Validate S3 access: aws s3 ls
  • If issues arise → check metadata service, remove local credentials, review bucket policies

Introduction

Here’s a scenario I’ve seen play out dozens of times: a developer hardcodes AWS access keys into an application running on EC2, pushes the code to GitHub, and within hours, someone in Eastern Europe is spinning up cryptocurrency miners on their account. The bill? $47,000. The fix? A 10-minute IAM role configuration that we will cover in this lab.

This hands-on AWS tutorial teaches you how to create an IAM role for EC2 and attach S3 read-only permissions — the right way. No access keys stored on the instance. No credentials in your code. Just clean, secure, temporary credentials that AWS rotates automatically.

Why does this matter in real AWS environments?

Every production workload I’ve architected in the last five years uses IAM roles for EC2. It’s not optional — it’s foundational. Whether your application needs to read configuration from S3, pull container images from ECR, or write logs to CloudWatch, the mechanism is identical. Master this pattern once, and you’ll use it hundreds of times throughout your career.

Who is this lab for?

This AWS beginner lab is designed for engineers who can launch an EC2 instance but haven’t yet connected the dots on AWS permissions. If you’ve ever wondered “how does my EC2 instance talk to other AWS services?” — you’re in the right place.

Architect Perspective

From a Solutions Architect standpoint, IAM roles represent the principle of least privilege in action. You’re granting exactly the permissions needed, nothing more. The credentials are temporary, automatically rotated, and never touch disk in plaintext. This is how AWS security is designed to work.

Common Beginner Mistakes

Before we dive in, let me save you some headaches. The three mistakes I see most often:

  1. Creating the role but forgetting to attach it to the instance
  2. Attaching the role but testing from a terminal that still has local AWS credentials configured (which take precedence)
  3. Using AmazonS3FullAccess when AmazonS3ReadOnlyAccess would suffice — violating least privilege from day one

Let’s fix all of that.


Lab Overview

What You’ll Build

By the end of this step-by-step lab, you’ll have an EC2 instance that can list and read objects from any S3 bucket in your account — without a single access key stored anywhere on the machine.

Skills You’ll Gain

  • Creating IAM roles with the EC2 trust policy
  • Attaching AWS managed policies to roles
  • Associating IAM roles with running EC2 instances
  • Validating instance credentials using the AWS CLI
  • Understanding the EC2 instance metadata service

Real-World Use Cases

This exact pattern powers countless production scenarios:

  • Application servers reading configuration files from S3
  • Backup scripts pulling data from S3 for disaster recovery testing
  • Log processors reading raw logs before transformation
  • Static asset servers serving content stored in S3
  • CI/CD agents downloading build artifacts

Where This Knowledge is Critical

If you’re pursuing AWS certifications (Solutions Architect, SysOps, Developer), IAM roles for EC2 appear on every exam. In job interviews, I ask candidates about this pattern within the first 15 minutes. In production, misconfiguring this leads to either security incidents or broken applications. There’s no middle ground.


Prerequisites

Before starting this lab, ensure you have:

  • An AWS account with IAM administrative access
  • A running EC2 instance (Amazon Linux 2 or Amazon Linux 2023 recommended)
  • SSH or Session Manager access to the instance
  • At least one S3 bucket in your account (even empty is fine)
  • Basic familiarity with the AWS Console navigation

If you haven’t launched an EC2 instance yet, complete Lab 0.1 — Launching Your First EC2 Instance first.


Step-by-Step Hands-On Lab

Step 1: Open the IAM Console and Start Role Creation

What to do:

Navigate to the IAM Console. From the AWS Management Console, type “IAM” in the search bar and select IAM under Services. In the left navigation panel, click Roles, then click the Create role button.

Console Path: AWS Console → IAM → Roles → Create role

Why it matters:

IAM roles are identity objects that don’t belong to a specific user — they’re assumed by AWS services, applications, or users temporarily. By creating a role specifically for EC2, you’re establishing a trust relationship that says “EC2 instances are allowed to assume this role.”

What you should see:

You should now see a wizard with the heading “Select trusted entity” and multiple options including AWS service, AWS account, Web identity, and SAML 2.0 federation.

Common misconfiguration:

Don’t click “Create user” by accident. Users get permanent credentials. Roles get temporary credentials. For EC2, you always want a role.


Step 2: Select AWS Service and Choose EC2

What to do:

Under “Trusted entity type,” select AWS service. In the “Use case” section, you’ll see a dropdown. Select EC2 from the common use cases list. Click Next.

Screenshot description: The page shows AWS service selected with a blue radio button. Below, EC2 appears highlighted in the use case dropdown with its description: “Allows EC2 instances to call AWS services on your behalf.”

Why it matters:

This step creates the trust policy — the JSON document that tells AWS “EC2 is allowed to assume this role.” Behind the scenes, AWS generates this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Common misconfiguration:

If you select Lambda, ECS, or another service here, EC2 instances won’t be able to assume this role. The trust relationship is service-specific.


Step 3: Attach the AmazonS3ReadOnlyAccess Policy

What to do:

On the “Add permissions” page, use the search box to find AmazonS3ReadOnlyAccess. Check the box next to this AWS managed policy. Click Next.

Screenshot description: The permissions policies page shows a search filter with “S3ReadOnly” entered. The policy AmazonS3ReadOnlyAccess appears with its description and an ARN of arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess.

Why it matters:

AWS managed policies are pre-built permission sets maintained by AWS. AmazonS3ReadOnlyAccess grants s3:Get*, s3:List*, and s3:Describe* actions on all S3 resources. It’s read-only — no ability to upload, delete, or modify objects.

What you should see:

The policy attached count should show “1 permissions policy attached” in the summary section.

Common misconfiguration:

Don’t select AmazonS3FullAccess unless you genuinely need write permissions. I’ve seen developers attach full access “just in case” and then forget about it. Six months later, a compromised instance deletes production data. Start restrictive, expand only when necessary.


Step 4: Name the Role and Create It

What to do:

On the “Name, review, and create” page, enter the role name: EC2-S3ReadOnlyRole. Optionally add a description like “Allows EC2 instances to read S3 buckets.” Review the trusted entities and permissions, then click Create role.

Screenshot description: The review page displays the role name field populated with EC2-S3ReadOnlyRole, showing one trusted entity (EC2 service) and one permission policy attached.

Why it matters:

Role naming conventions matter in production. I use the pattern {Service}-{Purpose}Role because it’s immediately clear what the role does when you’re troubleshooting at 2 AM. Avoid generic names like “MyRole” or “TestRole.”

What you should see:

A green success banner: “Role EC2-S3ReadOnlyRole created.”

Common misconfiguration:

Role names are permanent within an account — you can’t rename them. If you typo the name, you’ll need to delete and recreate. Double-check before clicking create.


Step 5: Attach the Role to an Existing EC2 Instance

What to do:

Navigate to the EC2 Console. Select your running instance. Click Actions → Security → Modify IAM role. In the dropdown, select EC2-S3ReadOnlyRole. Click Update IAM role.

Console Path: EC2 Console → Instances → Select Instance → Actions → Security → Modify IAM role

Screenshot description: The Modify IAM role page shows a dropdown with available roles. EC2-S3ReadOnlyRole appears in the list and is selected.

Why it matters:

This action creates an instance profile (a container for the role) and associates it with your EC2 instance. The instance can now request temporary credentials from the EC2 metadata service.

What you should see:

A success message: “Successfully attached EC2-S3ReadOnlyRole to instance i-xxxxxxxxx.”

Common misconfiguration:

The role won’t appear in the dropdown if you selected the wrong trusted entity in Step 2. Only roles with EC2 as the trusted service show up here.


Step 6: Connect to the Instance

What to do:

Connect to your EC2 instance using SSH or Session Manager.

For SSH:

ssh -i your-key.pem ec2-user@<public-ip>

For Session Manager:
Navigate to EC2 Console → Select instance → Connect → Session Manager → Connect

Why it matters:

You need a shell on the instance to verify that the role credentials are working. Session Manager is preferred in production since it doesn’t require open SSH ports.

What you should see:

A terminal prompt like [ec2-user@ip-10-0-1-50 ~]$


Step 7: Validate S3 Access with AWS CLI

What to do:

Run the following command inside the EC2 instance:

aws s3 ls

Why it matters:

This command lists all S3 buckets in your account. If the IAM role is attached correctly and the permissions are working, you’ll see output. No credentials configured, no access keys — just the role doing its job.

What you should see:

2024-01-15 10:30:22 my-first-bucket
2024-02-20 14:15:33 application-logs-bucket
2024-03-01 09:45:11 backup-data-bucket

Common misconfiguration:

If you see “Unable to locate credentials,” the role isn’t attached or the metadata service is disabled. We’ll troubleshoot this in Section H.


Real Lab Experiences (Architect Insights)

Let me share some hard-won lessons from production environments.

The Credential Precedence Trap

Last year, a junior engineer on my team spent four hours debugging why his EC2 instance “wasn’t using the IAM role.” The application kept failing with permission denied errors. The problem? Someone had previously run aws configure on the instance and entered invalid credentials. The AWS SDK found those credentials first and never checked the instance metadata.

The fix was simple: delete ~/.aws/credentials. But the lesson is critical — credential precedence in the AWS SDK follows this order: environment variables → credentials file → instance metadata. If any higher-priority source exists, the role is ignored.

The Metadata Service Version Issue

In 2021, AWS introduced IMDSv2, which requires session tokens for metadata requests. Some older AMIs or hardened configurations disable IMDSv1 entirely. If your role suddenly stops working after an AMI update, check the metadata service configuration.

Subtle Permission Boundaries

I once attached AmazonS3ReadOnlyAccess to a role, but the application still couldn’t read objects. Why? The S3 bucket had a bucket policy explicitly denying access from that specific role. Permission evaluation in AWS is complex: identity policies AND resource policies must both allow the action.

My Advice to Junior Engineers

Always test immediately after attaching a role. Don’t assume it works. Run aws sts get-caller-identity to confirm the role is being assumed, then test the actual operation you need. Five minutes of validation saves hours of debugging later.


Validation & Testing

After attaching the role, run these commands to confirm everything works:

Verify the role is being assumed:

aws sts get-caller-identity

Expected output:

{
    "UserId": "AROA3XXXXXXXXXXX:i-0abc123def456",
    "Account": "123456789012",
    "Arn": "arn:aws:sts::123456789012:assumed-role/EC2-S3ReadOnlyRole/i-0abc123def456"
}

The ARN should contain your role name. If it shows an IAM user ARN instead, local credentials are overriding the role.

List S3 buckets:

aws s3 ls

Read a specific object (if you have one):

aws s3 cp s3://your-bucket-name/test-file.txt -

Confirm write access is denied (expected failure):

echo "test" | aws s3 cp - s3://your-bucket-name/write-test.txt

Expected error:

upload failed: - to s3://your-bucket-name/write-test.txt An error occurred (AccessDenied) when calling the PutObject operation: Access Denied

This confirms least privilege is working — read succeeds, write fails.


Troubleshooting Guide

Quick Reference Table

IssueCauseFix
Unable to locate credentialsRole not attached or IMDS disabledRe-attach role in EC2 Console; enable metadata service
Wrong identity returnedLocal credentials override roleDelete ~/.aws/credentials; unset AWS_* env vars
AccessDenied despite correct roleBucket policy explicit deny or SCP restrictionReview bucket policy and organization SCPs
Metadata service timeoutIMDSv2 required but v1 request madeUse token-based requests (see below)
SSM connection failuresSSM agent can’t assume roleCheck journalctl -u amazon-ssm-agent -f

Issue 1: “Unable to locate credentials”

Symptoms: aws s3 ls returns “Unable to locate credentials. You can configure credentials by running aws configure.”

Debugging commands:

curl http://169.254.169.254/latest/meta-data/iam/security-credentials/

If this returns nothing or times out, the role isn’t attached or metadata is disabled.

Fixes:

  • Verify the role is attached in EC2 Console → Instance → Security tab
  • Check if IMDS is enabled: Instance → Actions → Instance settings → Modify instance metadata options

Issue 2: Metadata Service Timeout

Symptoms: curl to 169.254.169.254 hangs or times out.

Debugging:

curl -w "\n%{http_code}" --connect-timeout 2 http://169.254.169.254/latest/meta-data/

Fixes:

IMDSv2 might be required. Use token-based requests:

TOKEN=$(curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -H "X-aws-ec2-metadata-token: $TOKEN" http://169.254.169.254/latest/meta-data/iam/security-credentials/

Issue 3: Wrong Identity Returned

Symptoms: aws sts get-caller-identity shows an IAM user, not the role.

Debugging:

cat ~/.aws/credentials
env | grep AWS

Fixes:

  • Remove or rename ~/.aws/credentials
  • Unset environment variables: unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY

Issue 4: Access Denied Despite Correct Role

Symptoms: Role is attached, credentials work, but S3 operations fail.

Debugging:

Check for bucket policies or SCPs blocking access:

aws s3api get-bucket-policy --bucket your-bucket-name

Fixes:

  • Review bucket policies for explicit denies
  • Check AWS Organizations SCPs if applicable
  • Verify the role ARN matches allowed principals

Issue 5: SSM Agent Failures (for Session Manager users)

Debugging:

sudo journalctl -u amazon-ssm-agent -f

Look for credential or permission errors in the output.


AWS Best Practices (Solutions Architect Level)

Security

  • Never use access keys on EC2 — IAM roles eliminate the need entirely
  • Apply least privilege — start with ReadOnlyAccess, expand only when needed
  • Enable IMDSv2 — require token-based metadata requests to prevent SSRF attacks
  • Use VPC endpoints for S3 — keep traffic off the public internet

Reliability

  • Test role assumptions during instance launch with user data scripts
  • Monitor with CloudTrail — track all AssumeRole and S3 API calls
  • Implement health checks that verify S3 connectivity

Operational Excellence

  • Use consistent naming conventions{Service}-{Purpose}Role
  • Document role purposes in the description field
  • Automate role creation with CloudFormation or Terraform for repeatability

Cost Optimization

  • IAM roles are free — no cost for role creation or assumption
  • Reduce data transfer costs by using S3 VPC endpoints in the same region

Performance Efficiency

  • Credentials from the metadata service are cached by the SDK — no per-request latency penalty
  • Consider S3 Transfer Acceleration for cross-region reads if latency matters

Tagging Strategy

Tag your roles for governance:

Environment: Production
Application: WebApp
Owner: platform-team@company.com
CostCenter: CC-1234

Scaling Considerations

IAM roles scale infinitely — attach the same role to thousands of instances. For per-instance differentiation, use resource-based policies with instance IDs or tags as conditions.


Frequently Asked Questions (FAQs)

What is an IAM role for EC2?

An IAM role for EC2 is an AWS identity that grants temporary security credentials to applications running on EC2 instances. Instead of storing long-term access keys on the instance, the EC2 service assumes the role and provides rotating credentials through the instance metadata service. This eliminates credential management overhead and significantly improves security posture.

How do I attach an IAM role to an EC2 instance?

To attach an IAM role to an EC2 instance, navigate to the EC2 Console, select your instance, click Actions → Security → Modify IAM role, choose your role from the dropdown, and click Update IAM role. You can attach or change roles on running instances without stopping them. The new credentials become available within seconds.

What is the difference between an IAM user and an IAM role?

An IAM user has permanent long-term credentials (access keys) tied to a specific person or application, while an IAM role provides temporary credentials that are automatically rotated. Users are for human identities; roles are for AWS services, applications, or cross-account access. For EC2, always use roles because they eliminate the risk of credential exposure.

How does an EC2 instance get credentials from an IAM role?

EC2 instances retrieve credentials by querying the Instance Metadata Service (IMDS) at 169.254.169.254. When a role is attached, the metadata service returns temporary access keys, secret keys, and session tokens. The AWS SDK automatically handles this retrieval — your application code doesn’t need to explicitly fetch credentials.

Can I change the IAM role on a running EC2 instance?

Yes, you can attach, detach, or replace IAM roles on running EC2 instances without stopping or rebooting them. Navigate to EC2 Console → Select instance → Actions → Security → Modify IAM role. The change takes effect immediately, and applications using the AWS SDK will automatically pick up the new credentials.

What is an instance profile in AWS?

An instance profile is a container that holds an IAM role for EC2. When you attach a role to an EC2 instance through the console, AWS automatically creates an instance profile with the same name as the role. Think of it as a wrapper that allows EC2 to assume the role. You cannot attach an IAM role directly to EC2 — it must go through an instance profile.

Why should I use IAM roles instead of access keys on EC2?

IAM roles are more secure than access keys because credentials are temporary (expire in 1-6 hours), automatically rotated, never stored on disk in plaintext, and cannot be accidentally committed to source control. Access keys are permanent until manually rotated and create significant security risk if exposed. AWS strongly recommends roles for all EC2 workloads.

How do I verify that my EC2 instance is using an IAM role?

Run aws sts get-caller-identity on the instance. If the role is working, the output ARN will show assumed-role/YourRoleName/instance-id. If it shows an IAM user ARN instead, local credentials are overriding the role. You can also check the EC2 Console — select the instance, go to the Security tab, and verify the IAM Role field shows your role name.

What permissions does AmazonS3ReadOnlyAccess grant?

The AmazonS3ReadOnlyAccess managed policy grants s3:Get*, s3:List*, and s3:Describe* actions on all S3 resources in your account. This allows listing buckets, reading objects, viewing bucket configurations, and accessing metadata — but explicitly denies upload, delete, or modify operations. It’s the appropriate starting point for applications that only need to read from S3.

How long are IAM role credentials valid on EC2?

IAM role credentials obtained through the EC2 metadata service are valid for 1 to 6 hours, depending on the role’s maximum session duration setting. The AWS SDK automatically refreshes credentials before expiration — your application doesn’t need to handle rotation. You can verify the expiration time by querying the metadata service directly.

Why does my EC2 instance say “Unable to locate credentials” after attaching a role?

This error typically means the role isn’t properly attached, the instance metadata service is disabled, or local credentials are taking precedence. First, verify the role appears in the EC2 Console Security tab. Then check if IMDS is enabled. Finally, remove any ~/.aws/credentials file or AWS_ACCESS_KEY_ID environment variables that might override the role.

Can multiple EC2 instances use the same IAM role?

Yes, you can attach the same IAM role to unlimited EC2 instances. This is the recommended approach for fleets of identical servers running the same application. Each instance gets its own set of temporary credentials, but all instances share the same permissions defined in the role. IAM roles scale infinitely at no additional cost.

What is IMDSv2 and why does it matter for IAM roles?

IMDSv2 (Instance Metadata Service Version 2) is a more secure method for accessing EC2 metadata that requires session tokens for requests. It protects against SSRF attacks that could steal role credentials. AWS recommends requiring IMDSv2 on all instances. If your role stops working after enabling IMDSv2, update your applications to use token-based metadata requests.

How do I give an EC2 instance access to a specific S3 bucket only?

Instead of using the broad AmazonS3ReadOnlyAccess policy, create a custom IAM policy that specifies the bucket ARN in the Resource field. For example, allow s3:GetObject on arn:aws:s3:::my-specific-bucket/* only. Attach this custom policy to your EC2 role. This follows the principle of least privilege by limiting access to exactly what’s needed.

Do IAM roles cost money?

No, IAM roles are completely free. There is no charge for creating roles, attaching them to EC2 instances, or assuming roles. The only costs are for the AWS services your applications access using those role credentials (like S3 storage and requests). IAM itself has no pricing — it’s included with your AWS account.


Conclusion + Next Lab Recommendation

What You Achieved

You’ve successfully created an IAM role with S3 read-only permissions and attached it to an EC2 instance. Your instance can now securely access S3 without any stored credentials — using temporary, automatically rotated tokens provided by AWS STS.

Why It Matters

This pattern is the foundation of secure AWS architecture. Every application that interacts with AWS services should use IAM roles, not access keys. You’ve eliminated a major security vulnerability and implemented a best practice used in every well-architected AWS environment.


Related Labs & Resources


Have questions about this lab? Drop a comment below or reach out on Twitter. I read every message.

External References:

Similar Posts

One Comment

Leave a Reply