On Day 2 I wrote a least-privilege policy for one S3 bucket. The next question is who gets to use it. For people, that's a user or group. For servers and apps, it should be a role, so nothing long-lived ever sits on the machine.
Users vs roles
| IAM user | IAM role | |
|---|---|---|
| For | A person | A service, an app or a person who needs temporary access |
| Credentials | Long-term (password, access keys) | Temporary, issued on demand |
| Rotation | You do it | Automatic |
| Typical use | Console and CLI sign-in | EC2, Lambda, cross-account access |
Two policies on every role
-
Trust policy: who is allowed to assume the role (for example
ec2.amazonaws.com). - Permissions policy: what the role can do once assumed (for example the Day 2 S3 read policy).
A trust policy for EC2 looks like this:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": { "Service": "ec2.amazonaws.com" },
"Action": "sts:AssumeRole"
}
]
}
How EC2 gets credentials
- The role is attached to the instance through an instance profile (the console creates it for you when you make an EC2 role).
- Software on the instance asks the instance metadata service for credentials.
- STS (Security Token Service) issues a temporary access key, secret key and session token.
- AWS refreshes them automatically before they expire, so nothing is stored on disk.
Hands-on: try it in your own account
- Create a role: trusted entity AWS service, EC2.
- Attach the
ReadDemoBucketOnlypolicy from Day 2 (or any least-privilege S3 read policy). - Launch a small EC2 instance and attach the role under Actions, Security, Modify IAM role.
- Connect to the instance and run:
aws sts get-caller-identity
aws s3 ls s3://your-demo-bucket
aws s3 ls s3://some-other-bucket # should be denied
- To see the temporary credentials the instance receives (IMDSv2):
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/iam/security-credentials/
get-caller-identity should show an assumed-role ARN instead of a user, which proves the instance is using the role. Don't paste the credentials from the metadata call anywhere.
Common mistakes to avoid
- Putting access keys in code, config files or an AMI "just for now".
- Making the trust policy too wide, for example trusting a whole account when one role is enough.
- Attaching
AdministratorAccessto a role so the app "just works". - Forgetting the role is attached to the instance but the app still uses old keys from a config file.
3 habits I'm keeping
- Workloads get roles, people get users or federated sign-in, never the other way round.
- Keep trust and permissions separate and check both when access fails.
- Run
aws sts get-caller-identityfirst whenever something is denied.
Tomorrow (Day 4): protecting the root account and turning on MFA.
Top comments (0)