Identity and access management in AWS looks deceptively simple. You attach a few policies, wire roles into services, set up some single sign-on, and it all seems to work. Under the surface, though, there is a complex policy engine combining identity policies, resource policies, permission boundaries and organisation controls. Small misconfigurations in that engine often turn into full account compromise during a real incident.
This post focuses on AWS and looks at concrete IAM anti-patterns, such as misconfiguration and how they are exploited in practice, and what robust fixes look like. The aim is to give you a mental checklist you can use when reviewing an account or designing a new environment.
Quick recap of the AWS IAM model
AWS Identity and Access Management (IAM) is the control plane service that decides who can do what to which resources, and under which conditions.
At a high level:
- Identities include the root user, IAM users, IAM groups and IAM roles. Human access in modern AWS environments is increasingly handled by IAM Identity Center and external identity providers, with users assuming roles instead of relying on long lived IAM users.
- Roles and federated identities obtain temporary credentials from AWS Security Token Service (STS), rather than keeping passwords or access keys directly.
- Permissions are expressed as JSON policies attached to identities (identity based policies) and to resources such as S3 buckets or KMS keys (resource based policies).
- Each policy statement contains an Effect (Allow or Deny), an Action that names the API calls, a Resource that identifies which objects the statement applies to, and optional Condition blocks that further constrain access.
- When a principal sends a request, AWS evaluates all applicable policies. By default, requests are denied. An explicit Allow is required to authorise the action, and any explicit Deny overrides an Allow.
IAM roles are a special case because they combine two policies. A trust policy (a resource based policy on the role) controls who is allowed to call sts:AssumeRole, and a permissions policy (identity based policies attached to the role) controls what that role can do once assumed.
If you keep this mental model clear, the misconfigurations below become much easier to spot.
Top 5 AWS IAM misconfiguration patterns
Full admin and “:” policies everywhere
The most common anti-pattern in AWS IAM is the blanket allow policy, whether as the AWS managed AdministratorAccess policy or as a customer managed policy that contains Effect: Allow with Action set to “” and Resource set to ““. From a security point of view, these are functionally identical: compromise the principal and you own the account.
The risks are amplified when such policies are attached to long lived IAM users, cross account roles, CI pipelines or shared break glass accounts. A single leaked key or misused federated session becomes a complete failure of confidentiality, integrity and often availability.
A more subtle version of the same problem appears when a role is intended to be “admin in one area” but is granted global admin in practice, for example an operations role that only needs to manage EC2 but is given AdministratorAccess. Over time those roles are re used for automation, ad hoc troubleshooting and integration with third party tools. The policy surface grows while the actual required permissions stay small.
Fix pattern
You rarely need true account wide admin outside a very small number of incident response or platform engineering roles. Use job function managed policies and focused customer managed policies for day to day roles, and let service control policies (SCPs) enforce absolute safety rails such as blocking the deactivation of CloudTrail or GuardDuty. For any remaining full admin roles, keep them break glass only, with strong controls around authentication, monitoring and change management.
Overly permissive trust policies on roles
The trust policy on a role is often where cross account compromise begins. Examples of bad practice include:
- Using a wildcard in the Principal element, such as
"Principal": { "AWS": "*" }, without a restrictive Condition block. - Allowing entire external accounts to assume a role instead of a specific role in that account.
- Missing conditions such as aws:PrincipalOrgID, aws:SourceAccount or aws:SourceArn that would tie a role to a specific organisation, account or exact calling resource.
- Forgetting to use an external ID for third party access, which opens the door to confused deputy problems.
These issues are especially risky on roles with powerful permissions, such as deployment roles, logging roles, security tooling roles and roles that manage other IAM artefacts.
A simple but dangerous pattern looks like this: you create a role for a vendor, allow their account to assume it with a broad Principal, attach strong permissions, and never revisit the trust policy when their integration changes. Years later, anyone controlling that external account, or any other account that happens to match the Principal conditions, may be able to assume the role.
Fix pattern
Treat trust policies as carefully as you treat security group rules. For each role:
- Prefer specific principals, such as a concrete role ARN, over entire accounts.
- For cross account roles, combine a precise Principal with an external ID and, where possible, an organisation or account boundary condition.
- For roles used by AWS services such as Lambda or EventBridge, tighten trust policies with aws:SourceArn and aws:SourceAccount so that only the expected resource in the expected account can assume the role.
- Use IAM Access Analyzer to surface roles that are accessible from outside your organisation and review whether that exposure is still needed.
Dangerous wildcards in Action, Resource and Condition
Wildcards in policies are not inherently bad. They are required for patterns such as granting read access to all objects in a specific bucket, or allowing a role to describe all EC2 instances for inventory. Problems arise when wildcards are applied at the wrong boundary.
Examples include:
"Action": "iam:*"on a role assumed by an automation pipeline or external system."Resource": "*"in policies for powerful services such as IAM, KMS, CloudTrail, Organizations or CloudFormation.- Use of NotAction with Effect: Allow, which almost always grants more than intended.
- Overly broad use of condition keys that rely on tags that are not consistently enforced.
Even when a wildcard appears safe on day one, new services, new API calls and new resource types are added over time. That means a policy that was “wide but acceptable” can silently gain new powers in the future.
Fix pattern
Start with wildcards and then close them down using service specific resource ARNs, condition keys and permission boundaries. For example, instead of allowing “iam:*” on all resources for a CI role, define a boundary that only allows managing roles and policies whose names are prefixed for that pipeline. Use static analysis tools and managed rules such as those in AWS Config or third party scanners to flag policies that include full admin, NotAction with Allow, or risky service wide Actions.
Long lived IAM access keys and “service accounts”
Despite years of guidance to favour temporary credentials, many environments still rely heavily on IAM users with access keys for workloads, CI runners and operational scripts. Common symptoms include:
- Dozens or hundreds of IAM users whose only purpose is to hold an access key.
- Keys that have not been rotated for years.
- Root access keys that still exist despite best practice being to avoid them entirely.
- Access keys committed to source control or configuration management.
Once an attacker obtains a long lived key with meaningful permissions, they can operate from anywhere on the internet, often for a long time before anyone notices. Public key leaks in code repositories and configuration files are still one of the most common entry points in real incidents.
Fix pattern
Move workloads and humans away from IAM users wherever practical. Use IAM Identity Center or another identity provider for interactive access, and use roles for applications. Where IAM users remain necessary, enforce strong rotation policies, monitor for unused or anomalous keys, and use conditions in policies to pin key usage to expected IP ranges or VPC endpoints. Consider SCPs that prevent creation of new access keys outside specific break glass scenarios.
Unused roles, users and permissions
Over time, AWS accounts collect IAM artefacts that are no longer required but still have permissions attached. Examples include:
- Roles that were created for a proof of concept and never deleted.
- Old break glass accounts that were replaced by new access patterns.
- Customer managed policies that are no longer attached anywhere but still contain broad permissions.
- Roles that are only used by a single legacy workload that is itself rarely exercised.
These unused or under used principals often retain powerful permissions. From an attacker’s point of view they are ideal, because few people are watching their activity and automated tests are unlikely to cover them.
Fix pattern
Make unused access part of your regular hygiene. Use last accessed information and IAM Access Analyzer’s unused access features to find roles, users and policy statements that have not been exercised in months. Remove or reduce them. Build this into your routine, for example as a quarterly review, and codify decisions in infrastructure as code so that deletions are permanent rather than ad hoc.
Concrete exploit path: abusing iam:CreateAccessKey for privilege escalation
To see how these patterns converge, consider an attacker who gains access to a low privilege IAM user called app-support. That user is supposed to help with troubleshooting but has been granted a broad IAM policy during an incident years ago. The policy allows it to list IAM users and attached policies, and to call iam:CreateAccessKey on any user resource.
From the attacker’s perspective, a realistic path looks like this:
Enumerate IAM users and permissions
Using the compromised credentials, the attacker calls iam:ListUsers and iam:ListAttachedUserPolicies to find users that have powerful policies attached. They discover an IAM user named prod-admin that has the AdministratorAccess managed policy.
Create an access key for the admin user
The attacker then calls iam:CreateAccessKey with UserName set to prod-admin. Because IAM allows each user to have up to two access keys, this succeeds as long as prod-admin does not already have two active keys. The API response includes a new access key ID and secret access key for prod-admin.
This is exactly the escalation path documented in public research on AWS IAM privilege escalation, where iam:CreateAccessKey on other users is recognised as a high impact primitive.
Use the new keys to operate as the admin user
The attacker configures their tooling with the new access key pair. From this point onwards, every API call they make is authorised as prod-admin. Because that user has AdministratorAccess, the attacker can perform any action on any resource in the account, subject only to SCPs and other organisation level controls.
Establish persistence and cover tracks
With full admin rights, the attacker can create new IAM users and roles, attach powerful policies, change passwords or login profiles, and attempt to disable or evade logging. They can also create new access keys for other users, modify trust policies on roles, or push malicious changes through CI pipelines.
The entire chain depends on a single misconfiguration: granting iam:CreateAccessKey on other users to a low privilege identity. If that permission were restricted so that a user could only manage its own keys, or removed entirely in favour of roles, the escalation path would be blocked.
Detection: logs, queries and alerts that catch these issues
Strong IAM configuration needs strong observability. In AWS, that largely means CloudTrail, Config, GuardDuty and whatever SIEM or log analytics platform you use.
Build a logging baseline
Before focused detections, you need reliable audit trails:
- Enable at least one multi region CloudTrail trail that captures management events in all regions and delivers them to a central S3 bucket, ideally as an organisation trail if you use AWS Organizations.
- Keep CloudTrail logs for long enough to investigate slow burning incidents, not just the default 90 day event history.
- Use AWS Config to record configuration changes to IAM resources and CloudTrail itself so that tampering with logging can be detected.
- Enable GuardDuty across all accounts and regions so that anomalous API activity, unusual locations and credential behaviour are surfaced automatically based on CloudTrail, VPC flow logs and DNS logs.
Detect iam:CreateAccessKey misuse
Because access keys are a common persistence mechanism, you should treat CreateAccessKey events as high value signals.
Useful patterns include:
- Alert on iam:CreateAccessKey where the requesting principal is different from the target user and the target user has high privilege policies attached.
- Alert on iam:CreateAccessKey for the root user or any break glass user, which should normally never occur.
- Correlate new access keys with subsequent activity by the same key ID, for example unexpected access to CloudTrail, KMS, IAM or S3.
You can implement these either as CloudWatch metric filters and alarms on the CloudTrail log stream, as CloudTrail Lake queries backed by scheduled analytics jobs, or in your SIEM using its query language. AWS and several security vendors publish concrete examples of queries that pivot from a CreateAccessKey event to all actions taken by the resulting key ID during an incident.
Detect risky trust policy changes and IAM mutations
Other useful signals include:
- Changes to role trust policies that expand the Principal element or remove restrictive conditions.
- Attachment of AdministratorAccess or similar full privilege policies to additional users or roles.
- Creation of new inline policies on existing identities that grant iam:, sts:AssumeRole, kms: or organisations privileges.
- Creation, update or deletion of CloudTrail trails, log buckets, GuardDuty detectors or security configuration in general.
Many of these actions already have managed detection rules in GuardDuty, Security Hub, Config and common security monitoring platforms. Where they do not, combine CloudTrail events such as iam:UpdateAssumeRolePolicy, iam:AttachUserPolicy, iam:PutRolePolicy, kms:PutKeyPolicy and cloudtrail:StopLogging into a dedicated stream of “high risk control plane events” and require explicit review of any alert that hits this stream.
Remediation patterns and reusable policy templates
Fixing IAM misconfigurations at scale is as much about process and tooling as individual policies. The following patterns are useful across most AWS environments.
Pattern 1: Replace “:” with scoped permissions and guardrails
Start by finding policies that include Effect: "Allow", Action: "*", Resource: "*". For each:
- Identify what the principal actually needs to do, using CloudTrail and last accessed information to see real usage.
- Replace the blanket policy with one or more customer managed policies that grant only the required service actions on the specific resources.
- Where possible, attach a permissions boundary that restricts future changes so that even if someone adds broader permissions to the role, the boundary prevents misuse.
For example, instead of granting an operations role full IAM access, you might define a boundary that only allows managing roles whose names begin with ops- and forbids changing policies on logging, security and break glass roles.
Pattern 2: Harden trust policies
For roles that can be assumed from outside the account, rewrite trust policies so that they are explicit and defensive. A simple template for a cross account auditor role might look like this, with the account IDs and organisation IDs adjusted for your environment:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAuditorAccount",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/ExternalAuditRole"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"aws:PrincipalOrgID": "o-exampleorgid"
}
}
}
]
}
For roles assumed by AWS services, use templates from the documentation and add SourceArn and SourceAccount conditions that bind the role to the specific resource and account in which it should run.
Pattern 3: Constrain iam:CreateAccessKey and other high risk actions
High risk IAM actions include creating access keys, changing login profiles, attaching policies and updating role trust relationships. Wherever possible:
- Avoid granting these permissions at all to application roles or third party integrations.
- Where you must grant them, scope the Resource element so that a principal can only manage its own user record, not arbitrary users.
- Consider SCPs that block iam:CreateAccessKey for the root user and for specific sensitive users, forcing any remaining cases through tightly controlled automation.
For example, a policy that allows a user to manage only its own access keys can set the Resource to the specific user ARN and avoid wildcard resources.
Pattern 4: Systematically reduce long lived credentials
Design a migration away from IAM users and static keys:
- For workforce access, standardise on IAM Identity Center or another SAML or OIDC identity provider, with users assuming roles scoped to their job function.
- For workloads on EC2, ECS, EKS and Lambda, use IAM roles and service accounts with fine grained permissions instead of embedding keys in configuration.
- Audit IAM users with the credential report and IAM Access Analyzer unused access findings, and prioritise decommissioning users whose keys have not been used for months.
- Enforce policies that prevent creation of new IAM users for human access, except in tightly controlled break glass scenarios.
Pattern 5: Continuously right size permissions
Least privilege is not a one off exercise. As services evolve, code paths change and teams adopt new tools, permissions tend to grow. Use a combination of:
- IAM Access Analyzer policy generation to derive policies from observed access patterns in CloudTrail, then review and apply them to replace broad policies.
- Unused access findings to identify permissions, roles and users that have not been used in a chosen time window, and remove or narrow them.
- Regular reviews of high value roles, such as deployment, security, logging and break glass roles, looking at both their permissions policies and their trust policies.
Over time, this turns IAM from a static configuration that drifts into an actively managed security control with feedback from real usage.
Conclusion
IAM misconfigurations in AWS are rarely spectacular on their own. Most of the time they begin as small shortcuts taken during development, incident response or vendor onboarding. Combined with the flexibility of AWS policy evaluation and the constant addition of new services and features, those shortcuts can become serious weaknesses.
By understanding how identities, trust policies, permissions and logs fit together, and by focusing on specific anti-patterns like full admin policies, loose trust relationships, wildcards, long lived keys and unused access, you can dramatically reduce the blast radius of any single compromise. Pair that with strong logging, targeted detections and repeatable remediation patterns, and IAM becomes a powerful part of your defensive posture rather than a source of quiet risk.

