Quick Fix: Drop IAM Admin Privileges
Outcome
The IAM user or role will no longer be able to administer IAM, which means they will not be able to create, modify, or delete IAM users, roles, groups, or policies. This limits the blast radius of a compromised principal.
For example, after applying this fix:
- An IAM role
LegacyAppRolethat hadAdministratorAccesswill no longer be able to calliam:CreateUseroriam:AttachRolePolicy - An IAM user
former-contractorwill be unable to escalate their own privileges by attaching new policies
Fix
There are two approaches to deny IAM admin permissions: attach a Deny identity policy or set a permissions boundary. Choose the approach that fits your organization's IAM management practices.
Approach 1: Deny with an Identity Policy
Generate a Deny-IAM-Admin policy document that explicitly denies IAM administrative actions:
curl -o deny-iam-admin-policy.json \
https://api.k9security.io/policy/aws/identity/deny-iam-admin
This saves the policy document to deny-iam-admin-policy.json.
Option A: Create and attach a managed policy
# Set the target principal
PRINCIPAL_TYPE="role" # or "user" or "group"
PRINCIPAL_NAME="the-principal-to-restrict"
# Create the managed policy
aws iam create-policy \
--policy-name Deny-IAM-Admin \
--policy-document file://deny-iam-admin-policy.json \
--description "Denies IAM administrative actions to reduce excess admin privileges"
# Note the policy ARN from the output, then attach it
POLICY_ARN="arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/Deny-IAM-Admin"
# Attach to the principal
aws iam attach-${PRINCIPAL_TYPE}-policy \
--${PRINCIPAL_TYPE}-name "${PRINCIPAL_NAME}" \
--policy-arn "${POLICY_ARN}"
Option B: Embed an inline policy
PRINCIPAL_TYPE="role" # or "user" or "group"
PRINCIPAL_NAME="the-principal-to-restrict"
aws iam put-${PRINCIPAL_TYPE}-policy \
--${PRINCIPAL_TYPE}-name "${PRINCIPAL_NAME}" \
--policy-name DenyIAMAdminPrivs \
--policy-document file://deny-iam-admin-policy.json
Approach 2: Deny with a Permissions Boundary
A permissions boundary sets the maximum permissions an IAM entity can have.
PRINCIPAL_TYPE="role" # or "user"
PRINCIPAL_NAME="the-principal-to-restrict"
# Generate the permissions boundary policy document
curl -o deny-iam-admin-pb-policy.json \
https://api.k9security.io/policy/aws/permission-boundary/deny-iam-admin
# Create the permissions boundary managed policy
aws iam create-policy \
--policy-name Deny-IAM-Admin-PB \
--policy-document file://deny-iam-admin-pb-policy.json \
--description "Permissions boundary that denies IAM administrative actions"
POLICY_ARN="arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):policy/Deny-IAM-Admin-PB"
# Set the permissions boundary on the principal
aws iam put-${PRINCIPAL_TYPE}-permissions-boundary \
--${PRINCIPAL_TYPE}-name "${PRINCIPAL_NAME}" \
--permissions-boundary "${POLICY_ARN}"
Verify the fix
After applying the policy, verify the principal can no longer perform IAM admin actions:
# Simulate an IAM admin action against the principal
aws iam simulate-principal-policy \
--policy-source-arn "arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):${PRINCIPAL_TYPE}/${PRINCIPAL_NAME}" \
--action-names "iam:CreateUser" "iam:AttachRolePolicy" "iam:DeleteRole" \
--output table
All listed actions should show implicitDeny or explicitDeny in the EvalDecision column.
References
- k9 Security Kata 1: Review AWS IAM administrators - walkthrough for identifying excess IAM admins
- AWS: Permissions boundaries for IAM entities
- AWS: Policy evaluation logic
Gotcha
A permissions boundary only limits IAM users and roles -- it does not apply to IAM groups. If the principal's admin permissions come from group membership, you must either attach the Deny identity policy to the group itself (Approach 1) or remove the principal from the group. Check where the admin permissions are granted before choosing your approach.