← All articles
Cloud Security 8 min read

IAM Least Privilege in Practice: Moving from AdministratorAccess to Right-Sized Roles

A step-by-step guide to replacing AdministratorAccess with properly scoped IAM roles using AWS Access Analyzer, permission boundaries, and SCPs — with real policy JSON examples.

CloudDefender Team ·

The most dangerous four words in AWS security are “just give them admin.” A developer needs to deploy a Lambda function, so they get AdministratorAccess. A CI/CD pipeline needs to push to ECR, so it gets AdministratorAccess. Months later, those roles are still there, still have full access, and the developer has moved on to a different team. The blast radius when any of those credentials are stolen is total.

SCP (Service Control Policy) — Organization-wide ceilingEven AdministratorAccess cannot exceed this boundary. Applied at OU or account level.Permission Boundary — Maximum permissions for this identityAttached to the IAM user or role. Acts as an invisible fence — the intersection of this and identity policy is effective permissions.Identity Policy — What this principal is explicitly allowedThe role or user’s attached/inline policies. Effective only within both the permission boundary and SCP.Resource Policy — What the resource itself permitsS3 bucket policy, KMS key policy, SQS queue policy. Must allow the caller in addition to identity policy.Effective permission = intersection of all four layers
IAM permission evaluation layers. Every API call must clear all four. A denial at any layer is a final deny.

Why AdministratorAccess Is Endemic

The problem is structural. When engineers set up a new AWS environment, they often start with a single account and a single admin role. In that context, broad permissions feel harmless — it’s just a sandbox, after all. The issue is that environments mature and grow while permissions remain static. The CI/CD role that “just needed admin for the initial setup” never gets narrowed because narrowing it requires understanding exactly what it does, which requires time no one has.

The consequences of this accumulated privilege debt are severe. When an AdministratorAccess role’s credentials are compromised — whether through a leaked .env file, a compromised developer laptop, or a vulnerable EC2 instance running that role’s instance profile — the attacker inherits full account control. They can create IAM users, delete S3 buckets, spin up EC2 instances for crypto mining, exfiltrate every secret from Secrets Manager, and destroy your CloudTrail logs to cover their tracks. All of this is possible because at some point, someone said “just give them admin.”

AWS Access Analyzer’s GeneratedPolicy Feature

The most practical tool for moving away from AdministratorAccess is AWS Access Analyzer’s policy generation feature. The workflow is straightforward but requires patience: enable CloudTrail in all regions, then use Access Analyzer to observe 90 days of actual API calls made by the identity you want to right-size. Access Analyzer uses this CloudTrail data to generate a least-privilege policy reflecting only what was actually used.

The before/after comparison is often startling. A developer role with AdministratorAccess covering 600+ AWS action permissions might actually use fewer than 40 distinct actions over 90 days: ec2:DescribeInstances, lambda:UpdateFunctionCode, logs:FilterLogEvents, s3:GetObject on two specific buckets, and a handful of others. The generated policy captures exactly those 40 actions and, importantly, scopes s3:GetObject to the specific bucket ARNs that were actually accessed rather than "Resource": "*". This is the difference between a theoretical least-privilege policy and one grounded in observed behavior.

To generate a policy, navigate to IAM Access Analyzer in the AWS Console, select “Policy generation,” choose the IAM role or user, and specify a CloudTrail trail and date range. The generation takes a few minutes and produces a JSON policy you can review, edit, and apply.

Service Control Policies as a Hard Ceiling

Even if every role in your account had AdministratorAccess, a properly configured Service Control Policy at the AWS Organizations level can prevent the most destructive actions. SCPs act as a ceiling above IAM — they don’t grant permissions, they restrict what can ever be granted. An SCP that denies s3:DeleteBucket, iam:CreateUser, cloudtrail:StopLogging, and organizations:LeaveOrganization means those actions are impossible regardless of what any IAM policy says.

The key insight is that SCPs are applied at the AWS Organization OU or account level, not per-user. A single SCP attached to a production OU can enforce these restrictions across every principal in every account in that OU simultaneously. You don’t need to audit each role individually — the SCP provides a guarantee at the organizational boundary. For organizations using AWS Control Tower, many of these protective SCPs are provisioned automatically as “guardrails.”

Permission Boundaries: The Invisible Fence

Permission boundaries are an underused tool that solve a specific problem: preventing privilege escalation. The attack goes like this — a developer with iam:CreateRole can create a new role with AdministratorAccess, then assume that role, effectively granting themselves full admin even if their own role only had developer-level permissions. Permission boundaries break this chain.

A permission boundary is an IAM policy attached to a principal that defines the maximum permissions that principal can have. Even if the principal’s identity policy allows iam:*, if the permission boundary doesn’t include iam:CreateRole, that action is denied. Crucially, a principal cannot grant any permission that falls outside their own permission boundary — so a developer with a boundary that allows EC2/S3/Lambda but blocks IAM privilege escalation actions cannot create a role with permissions beyond that boundary, even if they have iam:CreateRole.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowedServices",
      "Effect": "Allow",
      "Action": [
        "ec2:*",
        "s3:*",
        "lambda:*",
        "logs:*",
        "cloudwatch:*",
        "ssm:GetParameter",
        "ssm:GetParameters"
      ],
      "Resource": "*"
    },
    {
      "Sid": "DenyPrivilegeEscalation",
      "Effect": "Deny",
      "Action": [
        "iam:CreateUser",
        "iam:CreateRole",
        "iam:AttachRolePolicy",
        "iam:AttachUserPolicy",
        "iam:PutRolePolicy",
        "iam:UpdateAssumeRolePolicy"
      ],
      "Resource": "*"
    }
  ]
}

This boundary allows a developer to do meaningful work while closing off the privilege escalation paths that attackers rely on.

Step-by-Step Migration from AdministratorAccess

The path from broad permissions to least privilege doesn’t require a big-bang cutover. A staged approach minimizes disruption while progressively tightening permissions.

Step 1: Enable CloudTrail in all regions. Multi-region trail logging ensures you capture API calls regardless of where they originate. Set the trail to log to an S3 bucket in a separate logging account that the workload accounts cannot modify.

Step 2: Enable Access Analyzer in all regions. This activates the data collection that will power policy generation. It also surfaces existing external access findings — IAM roles with trust policies allowing external accounts, public S3 buckets, and similar misconfigurations.

Step 3: Wait 2-4 weeks for activity data. The longer the observation window, the more complete the generated policy. 90 days is ideal, but 2-4 weeks captures most recurring workflows.

Step 4: Review and refine the generated policy. Access Analyzer’s output is a solid starting point, not a final answer. Review it for gaps — occasional workflows that didn’t run during the observation window — and for over-broad resource ARNs.

Step 5: Apply the generated policy as a permission boundary while keeping the original role. This is the low-risk validation step. The role’s identity policy still says AdministratorAccess, but the permission boundary limits effective access to the generated policy’s scope. Monitor CloudWatch and application logs for any AccessDenied errors over the following two weeks.

Step 6: Monitor for AccessDenied errors. Any AccessDenied in CloudTrail that didn’t exist before the boundary was applied indicates a gap in the generated policy. Add those actions and re-evaluate.

Step 7: Promote the generated policy to the principal policy, remove AdministratorAccess. Once you have confidence the boundary covers all legitimate use cases, make it the identity policy and remove AdministratorAccess from the attached policies entirely. The boundary can then be removed or kept as an additional safeguard.

A Right-Sized Developer Role

The following policy represents a realistic developer role for a team building serverless applications — broad enough to be productive, scoped enough to limit blast radius:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "LambdaAndLogsAccess",
      "Effect": "Allow",
      "Action": [
        "lambda:GetFunction",
        "lambda:UpdateFunctionCode",
        "lambda:UpdateFunctionConfiguration",
        "lambda:InvokeFunction",
        "lambda:GetFunctionConfiguration",
        "logs:DescribeLogGroups",
        "logs:FilterLogEvents",
        "logs:GetLogEvents"
      ],
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:myapp-*"
    },
    {
      "Sid": "S3ReadOnSpecificBuckets",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:ListBucket"],
      "Resource": [
        "arn:aws:s3:::myapp-assets-prod",
        "arn:aws:s3:::myapp-assets-prod/*"
      ]
    },
    {
      "Sid": "CloudWatchMetrics",
      "Effect": "Allow",
      "Action": ["cloudwatch:GetMetricStatistics", "cloudwatch:ListMetrics"],
      "Resource": "*"
    }
  ]
}

This role is scoped to specific Lambda function name prefixes and specific S3 bucket ARNs. If the credentials are stolen, the attacker can read from those assets and view logs — damaging, but not catastrophic. They cannot create IAM users, assume other roles, touch any other S3 bucket, or modify infrastructure. That containment is what least privilege actually looks like in practice.

CloudDefender

Defend your cloud. Continuously.

CloudDefender Suite gives security teams continuous posture management, threat detection, and compliance automation across AWS, Azure, and GCP — with zero false-positive fatigue.

Try CloudDefender →