IAM Customer-Managed Policies Should Not Allow Administrative Privileges
Overview
This check identifies customer-managed IAM policies that grant full administrative access through wildcard permissions (Action: "*" combined with Resource: "*"). Such policies give unrestricted access to every AWS service and resource in your account, violating the principle of least privilege.
Risk
Policies with *:* administrative privileges create severe security risks:
- Complete account compromise - An attacker with these permissions can access, modify, or delete any resource
- Data exfiltration - Unrestricted access to S3 buckets, databases, and secrets
- Privilege escalation - Ability to create new admin users or modify existing permissions
- Cover tracks - Permissions to disable CloudTrail, delete logs, and hide malicious activity
- Financial impact - Launch expensive resources or access billing information
This is rated as High severity because it represents a foundational security weakness.
Remediation Steps
Prerequisites
- AWS Console access with IAM permissions
- Ability to modify IAM policies (
iam:GetPolicy,iam:GetPolicyVersion,iam:CreatePolicyVersion,iam:DeletePolicyVersion) - Knowledge of what permissions the affected users/roles actually need
AWS Console Method
- Sign in to the AWS Management Console
- Navigate to IAM (Identity and Access Management)
- In the left navigation pane, click Policies
- Filter to show only Customer managed policies using the filter dropdown
- For each policy flagged by Prowler:
- Click the policy name to open its details
- Select the Permissions tab, then click JSON to view the policy document
- Look for statements with
"Action": "*"and"Resource": "*"
- Click Edit to modify the policy
- Replace the wildcard permissions with specific actions needed. For example:
- Change
"Action": "*"to specific actions like"Action": ["s3:GetObject", "s3:PutObject"] - Change
"Resource": "*"to specific ARNs like"Resource": "arn:aws:s3:::my-bucket/*"
- Change
- Click Next and then Save changes
Tip: Use IAM Access Analyzer to generate a policy based on actual usage rather than guessing what permissions are needed.
AWS CLI (optional)
Step 1: Identify policies with administrative privileges
List all customer-managed policies:
aws iam list-policies \
--scope Local \
--query 'Policies[*].[PolicyName,Arn,DefaultVersionId]' \
--output table \
--region us-east-1
Step 2: Review a specific policy
Get the policy document for inspection:
aws iam get-policy-version \
--policy-arn arn:aws:iam::<account-id>:policy/<policy-name> \
--version-id v1 \
--query 'PolicyVersion.Document' \
--output json \
--region us-east-1
Look for statements containing:
{
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
Step 3: Create a new, restricted policy version
Create a new version with least-privilege permissions:
aws iam create-policy-version \
--policy-arn arn:aws:iam::<account-id>:policy/<policy-name> \
--policy-document file://restricted-policy.json \
--set-as-default \
--region us-east-1
Where restricted-policy.json contains your scoped-down permissions.
Step 4: Delete the old overly permissive version
aws iam delete-policy-version \
--policy-arn arn:aws:iam::<account-id>:policy/<policy-name> \
--version-id v1 \
--region us-east-1
Note: You can have up to 5 policy versions. Delete old versions if needed.
CloudFormation (optional)
When defining IAM policies in CloudFormation, always use specific permissions instead of wildcards.
Bad example (triggers this check):
# DO NOT USE - This grants full administrative access
Resources:
BadAdminPolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: '*'
Resource: '*'
Good example (least privilege):
AWSTemplateFormatVersion: '2010-09-09'
Description: Example of a properly scoped IAM policy
Resources:
S3ReadWritePolicy:
Type: AWS::IAM::ManagedPolicy
Properties:
ManagedPolicyName: S3BucketAccessPolicy
Description: Grants read/write access to a specific S3 bucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AllowS3BucketAccess
Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:DeleteObject
- s3:ListBucket
Resource:
- arn:aws:s3:::my-application-bucket
- arn:aws:s3:::my-application-bucket/*
- Sid: AllowCloudWatchLogs
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource: arn:aws:logs:us-east-1:*:log-group:/aws/application/*
Deploy with:
aws cloudformation deploy \
--template-file iam-policy.yaml \
--stack-name scoped-iam-policy \
--capabilities CAPABILITY_NAMED_IAM \
--region us-east-1
Terraform (optional)
When defining IAM policies in Terraform, always use specific permissions.
Bad example (triggers this check):
# DO NOT USE - This grants full administrative access
resource "aws_iam_policy" "bad_admin_policy" {
name = "bad-admin-policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = "*"
Resource = "*"
}]
})
}
Good example (least privilege):
resource "aws_iam_policy" "s3_access_policy" {
name = "s3-bucket-access-policy"
description = "Grants read/write access to a specific S3 bucket"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowS3BucketAccess"
Effect = "Allow"
Action = [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
]
Resource = [
"arn:aws:s3:::my-application-bucket",
"arn:aws:s3:::my-application-bucket/*"
]
},
{
Sid = "AllowCloudWatchLogs"
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:us-east-1:*:log-group:/aws/application/*"
}
]
})
}
Using data sources to scope permissions:
data "aws_caller_identity" "current" {}
resource "aws_iam_policy" "dynamodb_access" {
name = "dynamodb-table-access"
policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Action = [
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem",
"dynamodb:DeleteItem",
"dynamodb:Query"
]
Resource = "arn:aws:dynamodb:us-east-1:${data.aws_caller_identity.current.account_id}:table/my-table"
}]
})
}
Verification
After modifying the policy, confirm it no longer grants administrative privileges:
- Go to IAM > Policies in the AWS Console
- Click on the modified policy name
- Review the JSON tab and confirm there are no
"Action": "*"with"Resource": "*"combinations - Re-run the Prowler check to verify remediation
CLI verification commands
Retrieve and inspect the current policy version:
# Get the policy details including default version
aws iam get-policy \
--policy-arn arn:aws:iam::<account-id>:policy/<policy-name> \
--region us-east-1
# Get the actual policy document
aws iam get-policy-version \
--policy-arn arn:aws:iam::<account-id>:policy/<policy-name> \
--version-id <default-version-id> \
--query 'PolicyVersion.Document' \
--region us-east-1
Re-run the Prowler check:
prowler aws --checks iam_customer_attached_policy_no_administrative_privileges
Additional Resources
- IAM Best Practices
- Grant Least Privilege
- IAM Access Analyzer Policy Generation
- AWS Managed Policies for Job Functions
- IAM Policy Simulator
Notes
- AWS-managed policies are excluded: This check only evaluates customer-created policies, not AWS-managed policies like
AdministratorAccess - Use Access Analyzer: Before restricting permissions, use IAM Access Analyzer to generate policies based on actual CloudTrail activity - this prevents breaking applications
- Permissions boundaries: Consider using permissions boundaries to limit the maximum permissions any identity can have
- Test in non-production first: Before applying restricted policies in production, test them in a development environment
- Gradual rollout: For critical systems, consider adding a deny policy first to log would-be denied actions before actually removing permissions
- Emergency access: Maintain a separate, tightly controlled break-glass procedure for emergency administrative access rather than granting permanent admin permissions