2026-01-27 17:35:45 -05:00
---
name : aws-iam
description : Manage IAM users, roles, and policies. Implement least-privilege access and security best practices. Use when configuring AWS identity and access management.
license : MIT
metadata :
author : devops-skills
version : "1.0"
---
# AWS IAM
2026-03-24 18:02:50 -04:00
Manage identity and access in AWS with least-privilege policies, roles, federation, and permission boundaries.
2026-01-27 17:35:45 -05:00
2026-03-24 18:02:50 -04:00
## When to Use This Skill
- Creating roles for EC2 instances, Lambda functions, or ECS tasks
- Writing custom IAM policies with least-privilege access
- Setting up OIDC federation for GitHub Actions or other CI/CD systems
- Implementing permission boundaries for delegated administration
- Auditing access with IAM Access Analyzer and credential reports
- Configuring cross-account access with assume-role patterns
- Enforcing MFA and session policies
## Prerequisites
- AWS CLI v2 installed and configured
- IAM permissions: `iam:*` (or scoped to specific actions for least privilege)
- For OIDC: ability to create identity providers (`iam:CreateOpenIDConnectProvider` )
- AWS Organizations access for Service Control Policies (SCPs)
## IAM Policy Structure
Every IAM policy follows the same JSON structure. Always specify the minimum actions and resources required.
2026-01-27 17:35:45 -05:00
```json
{
"Version" : "2012-10-17" ,
2026-03-24 18:02:50 -04:00
"Statement" : [
{
"Sid" : "AllowS3ReadWrite" ,
"Effect" : "Allow" ,
"Action" : [
"s3:GetObject" ,
"s3:PutObject" ,
"s3:ListBucket"
],
"Resource" : [
"arn:aws:s3:::my-app-bucket" ,
"arn:aws:s3:::my-app-bucket/*"
],
"Condition" : {
"StringEquals" : {
"s3:x-amz-server-side-encryption" : "aws:kms"
}
}
},
{
"Sid" : "DenyUnencryptedUploads" ,
"Effect" : "Deny" ,
"Action" : "s3:PutObject" ,
"Resource" : "arn:aws:s3:::my-app-bucket/*" ,
"Condition" : {
"StringNotEquals" : {
"s3:x-amz-server-side-encryption" : "aws:kms"
}
}
}
]
2026-01-27 17:35:45 -05:00
}
```
2026-03-24 18:02:50 -04:00
## Create and Manage Roles
2026-01-27 17:35:45 -05:00
```bash
2026-03-24 18:02:50 -04:00
# Create an EC2 instance role with trust policy
2026-01-27 17:35:45 -05:00
aws iam create-role \
--role-name EC2AppRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "ec2.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
2026-03-24 18:02:50 -04:00
}' \
--tags '[{"Key":"Team","Value":"platform"},{"Key":"Environment","Value":"production"}]'
# Create and attach an inline policy
aws iam put-role-policy \
--role-name EC2AppRole \
--policy-name s3-access \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-app-bucket/*"
}]
2026-01-27 17:35:45 -05:00
}'
2026-03-24 18:02:50 -04:00
# Attach a managed policy
2026-01-27 17:35:45 -05:00
aws iam attach-role-policy \
--role-name EC2AppRole \
2026-03-24 18:02:50 -04:00
--policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy
# Create instance profile and associate the role
aws iam create-instance-profile --instance-profile-name EC2AppProfile
aws iam add-role-to-instance-profile \
--instance-profile-name EC2AppProfile \
--role-name EC2AppRole
# Create a Lambda execution role
aws iam create-role \
--role-name LambdaExecRole \
--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 LambdaExecRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
2026-01-27 17:35:45 -05:00
```
2026-03-24 18:02:50 -04:00
## Cross-Account Access
2026-01-27 17:35:45 -05:00
```bash
2026-03-24 18:02:50 -04:00
# In Account B: create role that Account A can assume
aws iam create-role \
--role-name CrossAccountReadRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::111111111111:root"},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {"sts:ExternalId": "unique-external-id-12345"}
}
}]
}'
# In Account A: assume the role
aws sts assume-role \
--role-arn arn:aws:iam::222222222222:role/CrossAccountReadRole \
--role-session-name cross-account-session \
--external-id unique-external-id-12345
# Use the temporary credentials
export AWS_ACCESS_KEY_ID = "ASIAXXX"
export AWS_SECRET_ACCESS_KEY = "xxx"
export AWS_SESSION_TOKEN = "xxx"
2026-01-27 17:35:45 -05:00
```
2026-03-24 18:02:50 -04:00
## OIDC Federation for GitHub Actions
```bash
# Create the GitHub OIDC identity provider
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list "6938fd4d98bab03faadb97b34396831e3780aea1"
# Create a role for GitHub Actions with repo-scoped trust
aws iam create-role \
--role-name GitHubActionsDeployRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
}
}
}]
}'
# Attach deployment permissions to the role
aws iam attach-role-policy \
--role-name GitHubActionsDeployRole \
--policy-arn arn:aws:iam::123456789012:policy/DeploymentPolicy
```
GitHub Actions workflow usage:
2026-01-27 17:35:45 -05:00
```yaml
2026-03-24 18:02:50 -04:00
# .github/workflows/deploy.yml
permissions :
id-token : write
contents : read
jobs :
deploy :
runs-on : ubuntu-latest
steps :
- uses : aws-actions/configure-aws-credentials@v4
with :
role-to-assume : arn:aws:iam::123456789012:role/GitHubActionsDeployRole
aws-region : us-east-1
- run : aws sts get-caller-identity
2026-01-27 17:35:45 -05:00
```
2026-03-24 18:02:50 -04:00
## Permission Boundaries
2026-01-27 17:35:45 -05:00
2026-03-24 18:02:50 -04:00
```bash
# Create a permission boundary policy
aws iam create-policy \
--policy-name DeveloperBoundary \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowedServices",
"Effect": "Allow",
"Action": [
"s3:*",
"lambda:*",
"dynamodb:*",
"sqs:*",
"sns:*",
"logs:*",
"cloudwatch:*",
"ecr:*",
"ecs:*"
],
"Resource": "*"
},
{
"Sid": "DenyIAMChanges",
"Effect": "Deny",
"Action": [
"iam:CreateUser",
"iam:DeleteUser",
"iam:CreateRole",
"iam:DeleteRole",
"iam:AttachRolePolicy",
"iam:PutRolePermissionsBoundary",
"iam:DeleteRolePermissionsBoundary"
],
"Resource": "*"
},
{
"Sid": "DenyOutsideRegion",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["us-east-1", "us-west-2"]
},
"ForAnyValue:StringNotLike": {
"aws:PrincipalArn": "arn:aws:iam::*:role/admin-*"
}
}
}
]
}'
# Create a role with the permission boundary
aws iam create-role \
--role-name DeveloperRole \
--assume-role-policy-document file://trust-policy.json \
--permissions-boundary "arn:aws:iam::123456789012:policy/DeveloperBoundary"
```
## IAM Access Analyzer and Auditing
```bash
# Create an IAM Access Analyzer
aws accessanalyzer create-analyzer \
--analyzer-name account-analyzer \
--type ACCOUNT
# List findings (externally accessible resources)
aws accessanalyzer list-findings \
--analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/account-analyzer
# Generate credential report
aws iam generate-credential-report
aws iam get-credential-report --output text --query Content | base64 -d > credential-report.csv
# Find users with console access but no MFA
aws iam list-users --query "Users[].UserName" --output text | while read user; do
mfa = $( aws iam list-mfa-devices --user-name " $user " --query "MFADevices" --output text)
if [ -z " $mfa " ] ; then
echo "NO MFA: $user "
fi
done
# List all policies attached to a role
aws iam list-attached-role-policies --role-name EC2AppRole
aws iam list-role-policies --role-name EC2AppRole
# Get the last-accessed services for a role
aws iam generate-service-last-accessed-details --arn arn:aws:iam::123456789012:role/EC2AppRole
# Then retrieve results with the returned JobId
aws iam get-service-last-accessed-details --job-id "job-id-from-above"
# Simulate a policy to test access
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/EC2AppRole \
--action-names s3:GetObject s3:PutObject \
--resource-arns arn:aws:s3:::my-app-bucket/data.json
```
## Terraform IAM Role with OIDC
```hcl
# OIDC provider for GitHub Actions
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = [ "sts.amazonaws.com" ]
thumbprint_list = [ "6938fd4d98bab03faadb97b34396831e3780aea1" ]
}
# Role for GitHub Actions
resource "aws_iam_role" "github_actions" {
name = "GitHubActionsDeployRole"
assume_role_policy = jsonencode ( {
Version = "2012-10-17"
Statement = [ {
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider . github . arn
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:my-org/my-repo:*"
}
}
}]
})
permissions_boundary = aws_iam_policy . boundary . arn
}
resource "aws_iam_role_policy_attachment" "deploy" {
role = aws_iam_role . github_actions . name
policy_arn = aws_iam_policy . deployment . arn
}
# Permission boundary
resource "aws_iam_policy" "boundary" {
name = "DeveloperBoundary"
policy = jsonencode ( {
Version = "2012-10-17"
Statement = [
{
Sid = "AllowedServices"
Effect = "Allow"
Action = [ "s3:*", "lambda:*", "dynamodb:*", "ecs:*", "logs:*" ]
Resource = "*"
},
{
Sid = "DenyIAMEscalation"
Effect = "Deny"
Action = [ "iam:CreateUser", "iam:CreateRole", "iam:AttachRolePolicy" ]
Resource = "*"
}
]
})
2026-01-27 17:35:45 -05:00
}
```
2026-03-24 18:02:50 -04:00
## Service Control Policies (Organizations)
2026-01-27 17:35:45 -05:00
2026-03-24 18:02:50 -04:00
```json
{
"Version" : "2012-10-17" ,
"Statement" : [
{
"Sid" : "DenyRootAccount" ,
"Effect" : "Deny" ,
"Action" : "*" ,
"Resource" : "*" ,
"Condition" : {
"StringLike" : {
"aws:PrincipalArn" : "arn:aws:iam::*:root"
}
}
},
{
"Sid" : "RequireIMDSv2" ,
"Effect" : "Deny" ,
"Action" : "ec2:RunInstances" ,
"Resource" : "arn:aws:ec2:*:*:instance/*" ,
"Condition" : {
"StringNotEquals" : {
"ec2:MetadataHttpTokens" : "required"
}
}
},
{
"Sid" : "DenyRegionsOutsideUS" ,
"Effect" : "Deny" ,
"Action" : "*" ,
"Resource" : "*" ,
"Condition" : {
"StringNotEquals" : {
"aws:RequestedRegion" : [ "us-east-1" , "us-west-2" ]
},
"ForAnyValue:StringNotLike" : {
"aws:PrincipalArn" : [ "arn:aws:iam::*:role/OrganizationAdmin" ]
}
}
}
]
}
```
## Troubleshooting
| Problem | Cause | Fix |
|---|---|---|
| Access Denied on API call | Missing or incorrect policy | Use `simulate-principal-policy` to test; check resource ARN format |
| Role cannot be assumed | Trust policy does not include the caller | Verify Principal in trust policy matches caller ARN |
| OIDC federation fails | Thumbprint or audience mismatch | Verify OIDC provider URL, client ID list, and condition keys |
| Permission boundary blocks action | Boundary does not include the action | Add the action to the boundary; effective = identity AND boundary |
| Credential report shows stale keys | Keys not rotated in 90+ days | Rotate keys; disable unused access keys |
| Service-linked role creation fails | Organization SCP blocks iam:CreateServiceLinkedRole | Add exception in SCP for the specific service |
| Cross-account assume role fails | Missing ExternalId or wrong account | Verify ExternalId matches; check account number in Principal |
| MFA condition not enforced | Condition key not in policy | Add `aws:MultiFactorAuthPresent` condition |
2026-01-27 17:35:45 -05:00
## Related Skills
2026-03-24 18:02:50 -04:00
- [terraform-aws ](../terraform-aws/ ) - IaC deployment of IAM resources
- [aws-ec2 ](../aws-ec2/ ) - Instance profiles and roles
- [aws-lambda ](../aws-lambda/ ) - Lambda execution roles
- [aws-ecs-fargate ](../aws-ecs-fargate/ ) - ECS task and execution roles
- [access-review ](../../../compliance/governance/access-review/ ) - Access auditing and governance