June 2025
·3 min read
AWS IAM Best Practices for Developers: Least Privilege in Practice
Stop using AdministratorAccess everywhere. A practical guide to AWS IAM roles, policies, permission boundaries, and the least-privilege patterns every developer needs.
Introduction
- The most common AWS security mistake: attaching
AdministratorAccessto everything for convenience. - Least-privilege IAM is a forcing function for understanding what your app actually needs.
- This guide covers: users vs roles vs policies, writing minimal policies, permission boundaries, OIDC for CI/CD, and the IAM Access Analyzer.
Core Concepts: Users, Roles, Groups, Policies
- IAM User — a person or machine identity with long-lived credentials. Avoid for applications.
- IAM Role — assumed temporarily by a service, Lambda, EC2, or external identity (GitHub). Preferred.
- IAM Group — collection of users sharing policies. For human team members only.
- IAM Policy — JSON document that allows/denies actions on resources. Attached to roles/users/groups.
The Principle of Least Privilege
- Grant only the permissions required to complete a task — nothing more.
- Start with a deny-all baseline; add
Allowstatements one by one as you discover what's needed. - Tools to find minimum permissions: CloudTrail → IAM Access Advisor,
iam:SimulatePrincipalPolicy.
Writing Minimal IAM Policies
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ReadECRImages",
"Effect": "Allow",
"Action": [
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability"
],
"Resource": "arn:aws:ecr:ap-southeast-1:123456789012:repository/my-app/*"
},
{
"Sid": "GetAuthToken",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
}
]
}- Scope
Resourceto specific ARNs, not"*". - Use
StringEqualscondition keys to restrict to specific accounts/orgs. - Never use
"Action": "*"or"Resource": "*"in production policies.
Roles for Services (Never Long-Lived Keys)
- ECS tasks: attach a Task Execution Role (pull from ECR, write CloudWatch Logs) and a Task Role (app's runtime permissions).
- Lambda: a dedicated role per function — not a shared "lambda-role".
- EC2: instance profile with exactly the permissions that instance needs.
OIDC: Keyless CI/CD Authentication
- Replace
AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYin GitHub Actions with OIDC trust. - GitHub → AWS IAM OIDC provider → assume a role with a trust policy scoped to your repo.
{
"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",
"token.actions.githubusercontent.com:sub": "repo:myorg/my-repo:ref:refs/heads/main"
}
}
}]
}# .github/workflows/deploy.yml
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-deploy-role
aws-region: ap-southeast-1Permission Boundaries
- A permission boundary caps the maximum permissions a role can ever have — even if someone tries to attach a broader policy.
- Use case: granting developers the ability to create roles for their applications without letting them escalate to AdministratorAccess.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["ec2:*", "ecs:*", "ecr:*", "logs:*"],
"Resource": "*"
}]
}- Attach to all developer-created roles:
iam:CreateRolecondition requiresiam:PermissionsBoundary.
IAM Access Analyzer
- Identifies resources (S3, IAM roles, KMS keys) that are accessible from outside your account.
- Run findings automatically in CI:
aws accessanalyzer list-findings --analyzer-name <name>. - Policy validation:
aws accessanalyzer validate-policy --policy-document file://policy.json.
Audit and Monitoring
- Enable CloudTrail in all regions — every IAM API call is logged.
- Set up CloudWatch Alarm for
ConsoleSignInwith MFA = false. - IAM Access Advisor: which services has this role actually called? Remove uncalled services from policy.
- Periodically rotate or review unused credentials with IAM Credentials Report.
MFA and Root Account
- Enable MFA on the root account immediately — use a hardware key (YubiKey) if possible.
- Never use the root account for day-to-day operations; create an admin IAM user instead.
- Require MFA for sensitive actions via
aws:MultiFactorAuthPresentcondition.
Conclusion
- OIDC for CI/CD and task roles for services eliminate the need for any long-lived access keys.
- The Access Analyzer + CloudTrail combo surfaces over-permissioned roles before attackers do.
- Treat IAM policies as code: store in Terraform, review in pull requests, validate in CI.