AWS IAM Best Practices for Enterprise Security
AWS IAM best practices form the foundation of cloud security. Identity and Access Management controls who can do what in your AWS environment, and misconfigured policies remain one of the most common causes of cloud security breaches. Therefore, implementing least privilege access, role-based permissions, and continuous policy monitoring is critical for any production AWS deployment.
Most organizations start with overly permissive policies for convenience and never tighten them. Moreover, as teams grow and services multiply, permission sets become increasingly complex and difficult to audit. Consequently, a systematic approach — starting with restrictive defaults and granting access incrementally — prevents security incidents and simplifies compliance audits down the line.
Role-Based Access Design and Least Privilege
Design roles around job functions rather than individual users. Each role should carry the minimum permissions needed for its specific purpose, nothing more. Furthermore, prefer AWS managed policies for common, well-understood permission sets and reserve inline policies for resource-specific rules that should never be reused elsewhere.
The principle that trips up most teams is the difference between identity-based and resource-based policies. An identity-based policy attaches to a role or user and travels with that principal. In contrast, a resource-based policy — such as an S3 bucket policy or an SQS queue policy — attaches to the resource and can grant cross-account access without assuming a role. When both exist, AWS evaluates the union, but an explicit Deny in either always wins.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSpecificS3Access",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::my-app-data-prod",
"arn:aws:s3:::my-app-data-prod/*"
],
"Condition": {
"StringEquals": {
"s3:prefix": ["uploads/", "reports/"]
}
}
},
{
"Sid": "AllowDynamoDBAccess",
"Effect": "Allow",
"Action": [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:UpdateItem"
],
"Resource": "arn:aws:dynamodb:us-east-1:123456789:table/orders-*"
},
{
"Sid": "DenyDeleteOperations",
"Effect": "Deny",
"Action": [
"s3:DeleteBucket",
"dynamodb:DeleteTable",
"rds:DeleteDBInstance"
],
"Resource": "*"
}
]
}
Replacing Long-Lived Keys with Temporary Credentials
The single highest-impact change most teams can make is eliminating long-lived IAM user access keys. Static keys leak through committed source code, CI logs, and laptops, and they rarely get rotated. Instead, rely on temporary credentials issued by STS, which expire automatically within hours.
For workloads running on EC2, ECS, or EKS, attach an instance profile or use IAM Roles for Service Accounts so the SDK retrieves credentials transparently. For human access, federate through IAM Identity Center (formerly AWS SSO) backed by your corporate identity provider. For external CI systems such as GitHub Actions, use OIDC federation so the pipeline assumes a role with no stored secrets at all.
A common trust policy for GitHub OIDC restricts the assuming repository and branch explicitly. As a result, even a compromised pipeline in a different repository cannot assume your deployment role.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789: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"
}
}
}
]
}
Permission Boundaries
Permission boundaries set the maximum permissions that a role can ever have, even when its attached policies grant broader access. This matters most for delegated administration — letting platform teams create their own roles while guaranteeing those roles can never exceed the boundary. Additionally, boundaries block a classic privilege-escalation path where a developer with iam:CreateRole grants themselves administrator.
The effective permissions of a bounded role are the intersection of its identity policy and the boundary. In other words, an action is only allowed when both documents permit it. Therefore, a boundary that omits IAM write actions neutralizes any attached policy that tries to grant them.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowComputeServices",
"Effect": "Allow",
"Action": [
"lambda:*",
"ecs:*",
"ecr:*",
"logs:*",
"cloudwatch:*",
"xray:*"
],
"Resource": "*"
},
{
"Sid": "AllowDataServices",
"Effect": "Allow",
"Action": [
"dynamodb:*",
"s3:*",
"sqs:*",
"sns:*"
],
"Resource": "arn:aws:*:*:123456789:*"
},
{
"Sid": "DenyIAMEscalation",
"Effect": "Deny",
"Action": [
"iam:CreateUser",
"iam:CreateRole",
"iam:PutRolePolicy",
"iam:AttachRolePolicy",
"iam:DeleteRolePermissionsBoundary",
"organizations:*"
],
"Resource": "*"
}
]
}
Service Control Policies (SCPs)
SCPs apply guardrails across your entire AWS Organization. They cap what any account can do, regardless of how generous the IAM policies inside that account are. Furthermore, SCPs enforce compliance requirements such as region restrictions, mandatory encryption, and required resource tagging. Importantly, an SCP never grants permissions — it only filters what is otherwise allowed.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyNonApprovedRegions",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringNotEquals": {
"aws:RequestedRegion": ["us-east-1", "us-west-2", "eu-west-1"]
},
"ArnNotLike": {
"aws:PrincipalARN": "arn:aws:iam::*:role/OrganizationAdmin"
}
}
},
{
"Sid": "RequireIMDSv2",
"Effect": "Deny",
"Action": "ec2:RunInstances",
"Resource": "arn:aws:ec2:*:*:instance/*",
"Condition": {
"StringNotEquals": {
"ec2:MetadataHttpTokens": "required"
}
}
}
]
}
IAM Access Analyzer and Continuous Verification
IAM Access Analyzer continuously evaluates your policies for overly permissive or externally shared access. It flags resources reachable from outside your account or organization, surfaces unused roles and permissions, and validates policy syntax against more than a hundred best-practice checks. Additionally, its policy generation feature reads CloudTrail history and proposes a tightened least-privilege policy based on actions a principal actually used over a period.
A practical rollout sequence works well in production teams. First, generate a policy from CloudTrail for an existing over-permissioned role. Next, deploy it to staging and watch Access Analyzer for new findings. Then, promote it to production behind a permission boundary so any regression is contained. For broader cloud-security context, see the related guide on zero trust security architecture and the cloud security posture management guide. The official AWS IAM best practices documentation provides comprehensive guidelines.
When NOT to Over-Engineer IAM
Tight IAM has real costs, and ignoring them produces a different failure mode: shadow access. When developers cannot ship because every change needs a new policy review, they share an admin role through the back door, which is worse than a slightly broad but audited policy. Therefore, match the rigor to the blast radius — a sandbox account used for experimentation does not need the same granularity as a production payments account.
Likewise, resist splitting permissions so finely that you end up with hundreds of nearly identical roles nobody can reason about. Wildcards within a tightly scoped resource ARN are often more maintainable than enumerating every action. The goal is auditable least privilege, not maximum policy count, so prefer a small set of well-understood roles with clear boundaries over sprawling micro-policies.
Key Takeaways
- Eliminate long-lived access keys in favor of STS temporary credentials and OIDC federation
- Design roles around job functions and enforce ceilings with permission boundaries
- Apply organization-wide guardrails with SCPs for regions, encryption, and tagging
- Run Access Analyzer continuously and generate policies from real CloudTrail usage
- Match granularity to blast radius to avoid shadow access from overly rigid controls
In conclusion, AWS IAM best practices require a layered approach — role-based design, temporary credentials, permission boundaries, SCPs, and continuous monitoring. Start with restrictive defaults, use Access Analyzer to remove unused permissions, and enforce organization-wide guardrails. The effort invested in proper IAM configuration prevents the majority of cloud security incidents while keeping teams productive.