A Complete Guide to Managing IAM Roles for EC2 Instances
An Instance Profile is a container for an IAM role that you can use to pass role information to an EC2 instance when the instance starts. It serves as a vehicle to deliver temporary, automatically-rotating credentials to applications running on EC2 instances.
An IAM identity that defines a set of permissions for making AWS service requests. Unlike IAM users, roles don't have long-term credentials.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::example-bucket"
}
]
}
A container for an IAM role that allows EC2 instances to "assume" that role and its permissions.
# AWS CLI command to create an instance profile aws iam create-instance-profile \ --instance-profile-name MyInstanceProfile # Add role to instance profile aws iam add-role-to-instance-profile \ --role-name MyRole \ --instance-profile-name MyInstanceProfile
A component of EC2 that provides instance-specific data to applications, including temporary credentials from attached roles.
# Access credentials from instance metadata
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name
# Using AWS SDK (automatic)
import boto3
s3 = boto3.client('s3')
# SDK automatically uses instance profile credentials
# 1. Create IAM role with trust policy
cat > ec2-trust-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
# 2. Create the role
aws iam create-role \
--role-name S3ReadOnlyRole \
--assume-role-policy-document file://ec2-trust-policy.json
# 3. Attach policy to role
aws iam attach-role-policy \
--role-name S3ReadOnlyRole \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# 4. Create instance profile
aws iam create-instance-profile \
--instance-profile-name S3ReadOnlyProfile
# 5. Add role to instance profile
aws iam add-role-to-instance-profile \
--role-name S3ReadOnlyRole \
--instance-profile-name S3ReadOnlyProfile
Attach an instance profile when launching an EC2 instance:
Attach or replace an instance profile for a running instance:
Using AWS CLI:
# Attach instance profile to running instance aws ec2 associate-iam-instance-profile \ --instance-id i-1234567890abcdef0 \ --iam-instance-profile Name=S3ReadOnlyProfile # Replace instance profile on running instance aws ec2 replace-iam-instance-profile \ --instance-id i-1234567890abcdef0 \ --iam-instance-profile Name=NewProfile
From within the EC2 instance, you can retrieve credentials:
# IMDSv2 (more secure) 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/role-name # IMDSv1 (legacy) curl http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name
The response includes:
{
"Code": "Success",
"LastUpdated": "2021-09-17T16:43:21Z",
"Type": "AWS-HMAC",
"AccessKeyId": "ASIAIOSFODNN7EXAMPLE",
"SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"Token": "IQoJb3JpZ2luX2VjEHgaCXVzLWVhc3QtMSJGMEQCIBp...",
"Expiration": "2021-09-17T23:00:21Z"
}
AWS SDKs automatically use instance profile credentials:
# Python (boto3)
import boto3
# No credentials specified - SDK automatically
# uses instance profile credentials
s3 = boto3.client('s3')
response = s3.list_buckets()
# Node.js (AWS SDK)
const AWS = require('aws-sdk');
// No credentials specified
const s3 = new AWS.S3();
s3.listBuckets((err, data) => {
if (err) console.log(err, err.stack);
else console.log(data.Buckets);
});
# Java (AWS SDK)
AmazonS3 s3Client = AmazonS3ClientBuilder
.standard()
// No credentials specified
.build();
List buckets = s3Client.listBuckets();
Grant only the permissions needed for the application to function. Avoid using overly permissive policies.
# Bad practice - too permissive
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
]
}
# Good practice - specific permissions
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-bucket",
"arn:aws:s3:::my-app-bucket/*"
]
}
]
}
Refine permissions with condition keys to add additional security controls.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-app-bucket/*",
"Condition": {
"StringEquals": {
"aws:PrincipalTag/Environment": "Production"
},
"IpAddress": {
"aws:SourceIp": "192.0.2.0/24"
}
}
}
]
}
Allow EC2 instances in one account to access resources in another account.
# In Account A (where the resource is)
# Create a role that Account B can assume
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::ACCOUNT-B-ID:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:PrincipalArn": "arn:aws:iam::ACCOUNT-B-ID:role/EC2Role"
}
}
}
]
}
# In Account B (where the EC2 instance is)
# Allow the EC2 role to assume the role in Account A
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRole",
"Resource": "arn:aws:iam::ACCOUNT-A-ID:role/CrossAccountRole"
}
]
}
Allow an EC2 instance to assume different roles for different tasks.
# Python example of role chaining
import boto3
# Use instance profile credentials to create STS client
sts_client = boto3.client('sts')
# Assume another role
response = sts_client.assume_role(
RoleArn='arn:aws:iam::123456789012:role/SpecialAccessRole',
RoleSessionName='MyAppSession'
)
# Create a new session with the temporary credentials
temp_credentials = response['Credentials']
s3_client = boto3.client(
's3',
aws_access_key_id=temp_credentials['AccessKeyId'],
aws_secret_access_key=temp_credentials['SecretAccessKey'],
aws_session_token=temp_credentials['SessionToken']
)
# Now use s3_client with the assumed role's permissions
response = s3_client.list_buckets()
IMDSv2 is a session-oriented method that enhances security by requiring a token.
# Get session token (valid for up to 6 hours) TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" \ -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` # Use token to retrieve metadata curl -H "X-aws-ec2-metadata-token: $TOKEN" \ http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name
Enforce IMDSv2 when launching instances:
# Using AWS CLI aws ec2 run-instances \ --image-id ami-12345678 \ --instance-type t2.micro \ --metadata-options "HttpTokens=required"
IMDSv1 is the original method, which doesn't require a token but is less secure.
# Direct request without token curl http://169.254.169.254/latest/meta-data/iam/security-credentials/role-name
Security considerations:
# 1. Check if instance profile is attached aws ec2 describe-instances \ --instance-id i-1234567890abcdef0 \ --query "Reservations[0].Instances[0].IamInstanceProfile" # 2. Verify IMDS is accessible curl -s http://169.254.169.254/latest/meta-data/ # 3. Check if role exists in metadata curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/ # 4. Test permissions with AWS CLI aws s3 ls --debug # 5. Check IAM role permissions aws iam get-role --role-name MyRole aws iam list-attached-role-policies --role-name MyRole aws iam get-policy-version \ --policy-arn arn:aws:iam::aws:policy/policy-name \ --version-id v1
Netflix uses instance profiles extensively in their microservices architecture. Their custom deployment platform, Spinnaker, leverages instance profiles to allow EC2 instances to securely access various AWS services without managing credentials. This enables their auto-scaling groups to dynamically provision instances that can immediately access required resources.
OpenAI uses instance profiles to grant their AI training clusters secure access to S3 buckets containing training data and model artifacts. By using instance profiles, they can ensure that only specific EC2 instances running their training workloads can access sensitive data, while automatically rotating credentials for enhanced security.
Prime Video's content delivery infrastructure uses instance profiles to allow encoding servers to access media files in S3, publish to SNS topics for notifications, and write metadata to DynamoDB. This eliminates the need to manage API keys across thousands of instances and ensures that credentials are automatically rotated.
Audible's audiobook processing pipeline uses instance profiles to grant EC2 instances permissions to access raw audio files, process them, and store the results. Their workflow spans multiple AWS services, and instance profiles ensure that each component has only the permissions it needs.
# Instance profile for web servers
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::website-assets-bucket",
"arn:aws:s3:::website-assets-bucket/*"
]
},
{
"Effect": "Allow",
"Action": [
"dynamodb:Query",
"dynamodb:GetItem",
"dynamodb:PutItem"
],
"Resource": "arn:aws:dynamodb:region:account:table/users"
},
{
"Effect": "Allow",
"Action": [
"cloudwatch:PutMetricData",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "*"
}
]
}
# Instance profile for data processing
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::raw-data-bucket",
"arn:aws:s3:::raw-data-bucket/*",
"arn:aws:s3:::processed-data-bucket",
"arn:aws:s3:::processed-data-bucket/*"
]
},
{
"Effect": "Allow",
"Action": [
"sqs:ReceiveMessage",
"sqs:DeleteMessage",
"sqs:GetQueueAttributes"
],
"Resource": "arn:aws:sqs:region:account:data-processing-queue"
},
{
"Effect": "Allow",
"Action": [
"sns:Publish"
],
"Resource": "arn:aws:sns:region:account:processing-complete-topic"
}
]
}
# CloudFormation template excerpt
Resources:
WebServerRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: ec2.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
- arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
WebServerInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Roles:
- !Ref WebServerRole
WebServerAutoScalingGroup:
Type: AWS::AutoScaling::AutoScalingGroup
Properties:
LaunchTemplate:
LaunchTemplateId: !Ref WebServerLaunchTemplate
Version: !GetAtt WebServerLaunchTemplate.LatestVersionNumber
MinSize: 2
MaxSize: 10
DesiredCapacity: 2
VPCZoneIdentifier: !Ref Subnets
WebServerLaunchTemplate:
Type: AWS::EC2::LaunchTemplate
Properties:
LaunchTemplateData:
ImageId: !Ref AMI
InstanceType: t3.micro
IamInstanceProfile:
Name: !Ref WebServerInstanceProfile
UserData:
Fn::Base64: !Sub |
#!/bin/bash
# Instance bootstrapping code
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
# Instance profile for CI/CD runners
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:PutImage"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::artifacts-bucket",
"arn:aws:s3:::artifacts-bucket/*"
]
},
{
"Effect": "Allow",
"Action": [
"cloudformation:ValidateTemplate",
"cloudformation:DescribeStacks",
"cloudformation:CreateStack",
"cloudformation:UpdateStack"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"lambda:UpdateFunctionCode",
"lambda:PublishVersion",
"lambda:UpdateAlias"
],
"Resource": "arn:aws:lambda:region:account:function:*"
}
]
}
| Feature | Instance Profiles | IAM Users (Access Keys) | AWS Secrets Manager | ECS Task Roles |
|---|---|---|---|---|
| Credential Rotation | Automatic (hourly) | Manual | Manual or automated | Automatic (hourly) |
| Credential Storage | Instance metadata | Application code/config | Encrypted in AWS | Task metadata |
| Granularity | Per instance | Per user | Per secret | Per task |
| SDK Integration | Automatic | Manual configuration | Requires API calls | Automatic |
| Security Level | High | Medium | High | High |
| Use Case | EC2 instances | General purpose | Sensitive credentials | Containerized apps |