Give an Instance Role Access to S3

AWSBeginner
Practice Now

Introduction

Your EC2 application needs to read a report from S3. You will give the application an IAM role through an instance profile, verify its actual report read and observe what happens when you revoke its permission.

You should know EC2 launch, User Data and basic IAM policies. This fresh environment supplies an application image, network, key pair and an S3 bucket containing synthetic data. You will create the application instance and its authorization resources.

Certification Relevance

This lab practices least privilege and workload roles, supporting Task 2.3 of the AWS Certified Cloud Practitioner security objectives and Task 3.3 of its compute objectives.

Launch an Application Without a Role

In this step, you will launch the report server and observe that its application cannot yet read S3.

Start in your workspace and load the supplied image, network and report bucket IDs:

cd /home/labex/project
source launch.env

Inspect the supplied startup script. It configures the application's S3 bucket and object key, but supplies no credentials:

cat role-user-data.sh

Launch a server named role-server with this configuration:

aws ec2 \
  run-instances \
  --image-id "$AMI_ID" \
  --instance-type t3.micro \
  --subnet-id "$SUBNET_ID" \
  --security-group-ids "$SECURITY_GROUP_ID" \
  --key-name report-key \
  --count 1 \
  --user-data file://role-user-data.sh \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=role-server}]'

Save its ID:

INSTANCE_ID=$(aws ec2 \
  describe-instances \
  --filters Name=tag:Name,Values=role-server \
  --query 'Reservations[0].Instances[0].InstanceId' \
  --output text)
aws ec2 \
  wait instance-running \
  --instance-ids "$INSTANCE_ID"

Open AWS View, click Refresh resources, select role-server and click Check application. Confirm HTTP 200 and Role report server. Now click Read S3 report. Expect HTTP 503 and Application storage unavailable: the running application has no role credentials yet.

An application on EC2 can obtain temporary credentials for an assigned role through instance metadata. Its AWS SDK uses those credentials to sign API requests. The official EC2 role guide explains this workflow. You will configure the role rather than copy your terminal credentials to the application.

Grant Access Through an Instance Profile

In this step, you will create the role's trust and permission policies, put the role into an instance profile and associate it with your server.

A trust policy defines who may assume a role. Create a JSON policy that trusts the EC2 service. The quoted here-document preserves the JSON literally:

cat > ec2-trust.json <<'JSON'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "ec2.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }]
}
JSON

Create the role report-reader using that trust policy:

aws iam \
  create-role \
  --role-name report-reader \
  --assume-role-policy-document file://ec2-trust.json

A permissions policy defines what the assumed role can do. This application only needs s3:GetObject for report.csv. An object ARN includes both the bucket and key. Unlike the previous quoted here-document, the unquoted JSON marker below expands $REPORT_BUCKET into your supplied bucket name:

cat > read-report.json <<JSON
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "s3:GetObject",
    "Resource": "arn:aws:s3:::$REPORT_BUCKET/report.csv"
  }]
}
JSON

Inspect the resolved resource ARN:

cat read-report.json

Attach this inline policy to your role:

aws iam \
  put-role-policy \
  --role-name report-reader \
  --policy-name ReadReport \
  --policy-document file://read-report.json

An instance profile carries an IAM role to EC2. When using the CLI, you create the role and profile separately. Create a profile with the same name for clarity:

aws iam \
  create-instance-profile \
  --instance-profile-name report-reader

Add the role to the profile:

aws iam \
  add-role-to-instance-profile \
  --instance-profile-name report-reader \
  --role-name report-reader

Associate the profile with the existing instance and capture the association ID for cleanup:

ASSOCIATION_ID=$(aws ec2 \
  associate-iam-instance-profile \
  --instance-id "$INSTANCE_ID" \
  --iam-instance-profile Name=report-reader \
  --query 'IamInstanceProfileAssociation.AssociationId' \
  --output text)

Inspect the association:

aws ec2 \
  describe-iam-instance-profile-associations \
  --association-ids "$ASSOCIATION_ID" \
  --query 'IamInstanceProfileAssociations[].{Instance:InstanceId,State:State,Profile:IamInstanceProfile.Arn}'

Confirm associated and the report-reader profile. Refresh AWS View and click Read S3 report. Expect HTTP 200 with period,total and Q1,320. The application uses its SDK's role credentials to retrieve the actual S3 object. If the association is still propagating, wait briefly and retry the request.

The role grants one object read, rather than bucket-wide or administrator access. The instance profile guide explains how profiles carry roles and why association changes may take time to propagate.

Observe Permission Revocation

In this step, you will remove the role's report permission and distinguish authorization failure from application failure.

Remove only the inline policy you created:

aws iam \
  delete-role-policy \
  --role-name report-reader \
  --policy-name ReadReport

List the role's remaining inline policies:

aws iam \
  list-role-policies \
  --role-name report-reader

Expect an empty PolicyNames list. The instance profile remains associated, but its role no longer grants the report read.

In AWS View, click Check application and confirm HTTP 200: the server is still running. Then click Read S3 report. Expect HTTP 403 and Access denied. If permission changes have not propagated yet, wait briefly and repeat the request. A working health response alongside an S3 denial points to authorization, not a stopped instance or missing application.

Temporary credentials can be cached by an SDK. Removing a role permission changes what those credentials are allowed to do; removing an instance profile is not an immediate revocation of credentials already issued.

Clean Up the Instance and Role

In this step, you will remove your association, server, profile and role.

Disassociate the saved profile association:

aws ec2 \
  disassociate-iam-instance-profile \
  --association-id "$ASSOCIATION_ID"

Terminate the application instance:

aws ec2 \
  terminate-instances \
  --instance-ids "$INSTANCE_ID"
aws ec2 \
  wait instance-terminated \
  --instance-ids "$INSTANCE_ID"

Remove the role from its now-unused instance profile:

aws iam \
  remove-role-from-instance-profile \
  --instance-profile-name report-reader \
  --role-name report-reader

Delete the empty profile:

aws iam \
  delete-instance-profile \
  --instance-profile-name report-reader

The inline policy was already removed during revocation. Delete your role:

aws iam \
  delete-role \
  --role-name report-reader

Confirm the instance is terminated:

aws ec2 \
  describe-instances \
  --instance-ids "$INSTANCE_ID" \
  --query 'Reservations[].Instances[].{Instance:InstanceId,State:State.Name}'

Check that the named role and profile are absent:

aws iam \
  list-roles \
  --query "Roles[?RoleName=='report-reader'].RoleName"
aws iam \
  list-instance-profiles \
  --query "InstanceProfiles[?InstanceProfileName=='report-reader'].InstanceProfileName"

Both lists should be []. Refresh AWS View and confirm the server is no longer a running target. Leave the prepared S3 data, network and key pair in place.

Summary

You created EC2 trust and one-object S3 read permission for an IAM role, carried that role through an instance profile and verified the application's actual report read. You then revoked the permission and distinguished an S3 denial from a healthy application. Finally, you removed the instance, profile and role.