IAM and Security Basics
AWS Identity and Access Management (IAM) is the control plane for who can access what in your AWS account. Every API call, console login, and service-to-service interaction is authorized through IAM. Getting IAM right is the single highest-impact security decision in AWS.
Core Concepts
| Concept | Description | When to Use |
|---|---|---|
| User | Long-lived identity for humans or legacy apps | Individual developers (prefer SSO in production) |
| Group | Collection of users sharing permissions | Developers, Admins, ReadOnly |
| Role | Temporary credentials via STS assume-role | EC2, Lambda, cross-account, federated login |
| Policy | JSON document defining Allow/Deny actions | Attached to users, groups, or roles |
| Permission boundary | Maximum permissions a role/user can receive | Delegation scenarios |
Policy Structure
IAM policies are JSON documents evaluated on every request:
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowS3ReadOnProjectBucket",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-project-assets",
"arn:aws:s3:::my-project-assets/*"
],
"Condition": {
"IpAddress": {
"aws:SourceIp": "203.0.113.0/24"
}
}
}]
}
Policy Evaluation Logic
- Explicit Deny always wins
- Explicit Allow grants access if no Deny matches
- Default is implicit Deny
Create Users and Groups (CLI)
# Create a developers group
aws iam create-group --group-name Developers
# Attach a managed policy
aws iam attach-group-policy \
--group-name Developers \
--policy-arn arn:aws:iam::aws:policy/PowerUserAccess
# Create user and add to group
aws iam create-user --user-name jane-dev
aws iam add-user-to-group --user-name jane-dev --group-name Developers
# Create access keys (prefer SSO/roles in production)
aws iam create-access-key --user-name jane-dev
IAM Roles — The Preferred Pattern
Roles provide temporary credentials (15 min–12 hours). No long-lived keys to leak.
EC2 Instance Role
# Trust policy — who can assume this role
cat > ec2-trust.json << 'EOF'
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}
EOF
aws iam create-role \
--role-name EC2-S3-ReadRole \
--assume-role-policy-document file://ec2-trust.json
aws iam attach-role-policy \
--role-name EC2-S3-ReadRole \
--policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
# Create instance profile and attach role
aws iam create-instance-profile --instance-profile-name EC2-S3-Profile
aws iam add-role-to-instance-profile \
--instance-profile-name EC2-S3-Profile \
--role-name EC2-S3-ReadRole
On the EC2 instance, the AWS SDK automatically picks up credentials from the instance metadata service (IMDSv2):
import boto3
# No keys needed — uses instance role
s3 = boto3.client('s3')
objects = s3.list_objects_v2(Bucket='my-project-assets')
Lambda Execution Role
Every Lambda function needs an execution role:
aws iam create-role \
--role-name LambdaBasicExecution \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
aws iam attach-role-policy \
--role-name LambdaBasicExecution \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Least Privilege in Practice
Instead of AdministratorAccess, scope policies to specific actions:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789012:table/Orders"
}]
}
Use IAM Access Analyzer and policy simulator to validate:
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/jane-dev \
--action-names s3:ListBucket \
--resource-arns arn:aws:s3:::my-bucket
MFA Enforcement
Require MFA for sensitive operations:
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}]
}
Attach this as a separate policy alongside Allow policies (Deny overrides Allow).
Cross-Account Access
Account A (workload) trusts Account B (CI/CD):
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::999999999999:root"},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "unique-external-id-here"
}
}
}]
}
Real-World Scenario: Production IAM Layout
| Identity | Type | Permissions |
|---|---|---|
| CI/CD pipeline | Role in prod account | Deploy to ECS, push to ECR only |
| Developer | SSO → ReadOnly in prod, Full in dev | No prod write access |
| Lambda functions | Per-function execution roles | DynamoDB table X only |
| EC2 app servers | Instance role | S3 bucket Y, Secrets Manager secret Z |
AWS Managed vs Customer Managed Policies
| Type | Pros | Cons |
|---|---|---|
| AWS Managed | Maintained by AWS, quick setup | Often too broad |
| Customer Managed | Tailored, reusable, versioned | You maintain them |
| Inline | Tied to one user/role | Hard to audit at scale |
Prefer customer managed policies for production.
Common Mistakes
- AdministratorAccess everywhere — use scoped policies per workload
- Long-lived access keys on servers — use instance roles instead
- Wildcard resources (
"Resource": "*") without conditions — scope to ARNs - Ignoring service control policies (SCPs) in Organizations — IAM Allow can be blocked by SCP Deny
- Not enabling IAM Access Analyzer — free tool that finds overly permissive resources
Troubleshooting
| Error | Diagnosis | Fix |
|---|---|---|
AccessDenied |
Missing Allow or explicit Deny | Check attached policies, SCPs, permission boundaries |
Not authorized to perform sts:AssumeRole |
Trust policy mismatch | Verify Principal ARN and ExternalId |
Invalid principal in policy |
Typo in ARN | Use IAM policy validator in console |
| Works in console, fails in CLI | Different identity | Compare ARNs with aws sts get-caller-identity |
Best Practices Summary
- Prefer roles over users for applications and services
- Use IAM Identity Center (SSO) for human access in production
- Enable MFA on all privileged accounts
- Apply least privilege — start with zero, add permissions as needed
- Rotate access keys every 90 days; better yet, eliminate them
- Enable CloudTrail to audit IAM changes
- Use permission boundaries when delegating IAM administration
Next: EC2 — Virtual Servers.