Harden AWS IAM Security
An AWS IAM hardening expert - finds overpermissive/wildcard policies, missing MFA, and stale keys via CLI, with least-privilege policy templates.
Maintainer of this project? Claim this page to edit the listing.
15.5.1Add to Favorites
Why it matters
Automate the review and hardening of AWS IAM policies to enforce least privilege and enhance security posture.
Outcomes
What it gets done
Identify and remediate overly permissive IAM policies.
Enforce Multi-Factor Authentication (MFA) for all users.
Manage and rotate access keys to reduce exposure.
Analyze role trust relationships and policy usage.
Install
Add it to your toolbox
Run in your project directory:
curl -fsSL https://spark.entire.vc/get/ag-aws-iam-best-practices | bash Overview
AWS IAM Best Practices
An AWS IAM hardening expert with CLI checks for overpermissive/wildcard policies, missing MFA, and stale access keys, plus ready-made least-privilege, MFA-required, time-boxed, and IP-restricted policy templates. Use when IAM policies need reviewing, least privilege needs implementing, or IAM security generally needs hardening.
What it does
Reviews and hardens IAM policies following AWS security best practices and least-privilege principles, anchored on three core commitments: least privilege (minimum needed permissions, managed policies over inline ones, no wildcard actions, regular reviews), defense in depth (MFA for all users, roles instead of long-term access keys, service control policies, CloudTrail enabled), and separation of duties (distinct admin and user roles, different roles per environment, approval workflows, regular permission audits). Concrete CLI checks scan for overly permissive policies by grepping policy documents for a bare wildcard action, flag inline policies attached directly to users that should be managed policies instead, list users lacking MFA from the credential report, check whether a policy actually enforces aws:MultiFactorAuthPresent, find access keys older than 90 days by comparing their creation date to today, identify IAM roles that have never been used, and flag roles whose trust policy grants access to an external AWS account. A rotate-access-key pattern creates the replacement key first, prints the exact command to delete the old one once applications are updated, and deactivates rather than immediately deletes the old key so it can be tested safely before final removal.
Four ready-made policy templates cover common hardening patterns, starting with least-privilege S3 access scoped to a user's own prefix via a policy variable:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/user-data/${aws:username}/*"
},
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket",
"Condition": {"StringLike": {"s3:prefix": "user-data/${aws:username}/*"}}
}
]
}
plus an MFA-required policy that denies all actions unless MFA is present, a time-boxed policy that only grants EC2 access within a specific date range, and an IP-restricted policy denying all actions from outside two named CIDR ranges. A 20-item hardening checklist spans user management (MFA, unused user removal, 90-day key rotation, roles over long-term credentials, password policy), policy management (replacing inline policies, removing wildcards, least privilege, condition-based policies, regular reviews), role management (EC2 instance roles, cross-account trust review, unused role removal, session tags), and monitoring (CloudTrail for IAM events, CloudWatch alarms on IAM changes, IAM Access Analyzer, privilege-escalation monitoring). A companion Python script automates three of these checks directly against the IAM API - users without MFA, keys older than 90 days, and policies with a bare wildcard action - and prints a consolidated report.
When to use - and when NOT to
Use it when IAM policies need reviewing, least privilege needs implementing, or IAM security generally needs hardening. Best practices reinforce using AWS managed policies where possible, versioning policies, testing changes in non-production first, documenting each policy's purpose, running access reviews quarterly, using IAM Access Analyzer, and layering service control policies for organization-wide guardrails on top of individual IAM policies.
Inputs and outputs
Takes an AWS account's IAM configuration; produces a report of overpermissive policies, missing MFA, stale access keys, and unused roles, plus ready-to-apply least-privilege, MFA-required, time-boxed, or IP-restricted policy documents.
Who it's for
Teams auditing or hardening AWS IAM who want concrete CLI checks and ready-made policy templates rather than reading the best-practices documentation and writing checks from scratch.
Source README
AWS IAM Best Practices
Review and harden IAM policies following AWS security best practices and least privilege principles.
When to Use
Use this skill when you need to review IAM policies, implement least privilege access, or harden IAM security.
Core Principles
Least Privilege
- Grant minimum permissions needed
- Use managed policies when possible
- Avoid wildcard (*) permissions
- Regular access reviews
Defense in Depth
- Enable MFA for all users
- Use IAM roles instead of access keys
- Implement service control policies (SCPs)
- Enable CloudTrail for audit
Separation of Duties
- Separate admin and user roles
- Use different roles for different environments
- Implement approval workflows
- Regular permission audits
IAM Security Checks
Find Overly Permissive Policies
### List policies with full admin access
aws iam list-policies --scope Local \
--query 'Policies[*].[PolicyName,Arn]' --output table | \
grep -i admin
### Find policies with wildcard actions
aws iam list-policies --scope Local --query 'Policies[*].Arn' --output text | \
while read arn; do
version=$(aws iam get-policy --policy-arn "$arn" \
--query 'Policy.DefaultVersionId' --output text)
doc=$(aws iam get-policy-version --policy-arn "$arn" \
--version-id "$version" --query 'PolicyVersion.Document')
if echo "$doc" | grep -q '"Action": "\*"'; then
echo "Wildcard action in: $arn"
fi
done
### Find inline policies (should use managed policies)
aws iam list-users --query 'Users[*].UserName' --output text | \
while read user; do
policies=$(aws iam list-user-policies --user-name "$user" \
--query 'PolicyNames' --output text)
if [ -n "$policies" ]; then
echo "Inline policies on user $user: $policies"
fi
done
MFA Enforcement
### List users without MFA
aws iam get-credential-report --output text | \
awk -F, 'NR>1 && $4=="false" {print $1}'
### Check if MFA is required in policies
aws iam list-policies --scope Local --query 'Policies[*].Arn' --output text | \
while read arn; do
version=$(aws iam get-policy --policy-arn "$arn" \
--query 'Policy.DefaultVersionId' --output text)
doc=$(aws iam get-policy-version --policy-arn "$arn" \
--version-id "$version" --query 'PolicyVersion.Document')
if echo "$doc" | grep -q "aws:MultiFactorAuthPresent"; then
echo "MFA enforced in: $arn"
fi
done
### Enable MFA for a user (returns QR code)
aws iam create-virtual-mfa-device \
--virtual-mfa-device-name user-mfa \
--outfile /tmp/qr.png \
--bootstrap-method QRCodePNG
Access Key Management
### Find old access keys (>90 days)
aws iam list-users --query 'Users[*].UserName' --output text | \
while read user; do
aws iam list-access-keys --user-name "$user" \
--query 'AccessKeyMetadata[*].[AccessKeyId,CreateDate,Status]' \
--output text | \
while read key_id create_date status; do
age_days=$(( ($(date +%s) - $(date -d "$create_date" +%s)) / 86400 ))
if [ $age_days -gt 90 ]; then
echo "$user: Key $key_id is $age_days days old"
fi
done
done
### Rotate access key
OLD_KEY="<AWS_ACCESS_KEY_ID>"
USER="myuser"
### Create new key
NEW_KEY=$(aws iam create-access-key --user-name "$USER")
echo "New key created. Update applications, then run:"
echo "aws iam delete-access-key --user-name $USER --access-key-id $OLD_KEY"
### Deactivate old key (test first)
aws iam update-access-key \
--user-name "$USER" \
--access-key-id "$OLD_KEY" \
--status Inactive
Role and Policy Analysis
### List unused roles (no activity in 90 days)
aws iam list-roles --query 'Roles[*].[RoleName,RoleLastUsed.LastUsedDate]' \
--output text | \
while read role last_used; do
if [ "$last_used" = "None" ]; then
echo "Never used: $role"
fi
done
### Find roles with trust relationships to external accounts
aws iam list-roles --query 'Roles[*].RoleName' --output text | \
while read role; do
trust=$(aws iam get-role --role-name "$role" \
--query 'Role.AssumeRolePolicyDocument')
if echo "$trust" | grep -q '"AWS":'; then
echo "External trust: $role"
fi
done
### Analyze policy permissions
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:user/myuser \
--action-names s3:GetObject s3:PutObject \
--resource-arns arn:aws:s3:::mybucket/*
IAM Policy Templates
Least Privilege S3 Access
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::my-bucket/user-data/${aws:username}/*"
},
{
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket",
"Condition": {
"StringLike": {
"s3:prefix": "user-data/${aws:username}/*"
}
}
}
]
}
MFA-Required Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"BoolIfExists": {
"aws:MultiFactorAuthPresent": "false"
}
}
}
]
}
Time-Based Access
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*",
"Condition": {
"DateGreaterThan": {
"aws:CurrentTime": "2026-01-01T00:00:00Z"
},
"DateLessThan": {
"aws:CurrentTime": "2026-12-31T23:59:59Z"
}
}
}
]
}
IP-Restricted Access
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"NotIpAddress": {
"aws:SourceIp": [
"203.0.113.0/24",
"198.51.100.0/24"
]
}
}
}
]
}
IAM Hardening Checklist
User Management
- Enable MFA for all users
- Remove unused IAM users
- Rotate access keys every 90 days
- Use IAM roles instead of long-term credentials
- Implement password policy (length, complexity, rotation)
Policy Management
- Replace inline policies with managed policies
- Remove wildcard (*) permissions
- Implement least privilege
- Use policy conditions (MFA, IP, time)
- Regular policy reviews
Role Management
- Use roles for EC2 instances
- Implement cross-account roles properly
- Review trust relationships
- Remove unused roles
- Use session tags for fine-grained access
Monitoring
- Enable CloudTrail for IAM events
- Set up CloudWatch alarms for IAM changes
- Use AWS IAM Access Analyzer
- Regular access reviews
- Monitor for privilege escalation
Automated IAM Hardening
#!/usr/bin/env python3
### iam-hardening.py
import boto3
from datetime import datetime, timedelta
iam = boto3.client('iam')
def enforce_mfa():
"""Identify users without MFA"""
users = iam.list_users()['Users']
no_mfa = []
for user in users:
mfa_devices = iam.list_mfa_devices(
UserName=user['UserName']
)['MFADevices']
if not mfa_devices:
no_mfa.append(user['UserName'])
return no_mfa
def rotate_old_keys():
"""Find access keys older than 90 days"""
users = iam.list_users()['Users']
old_keys = []
for user in users:
keys = iam.list_access_keys(
UserName=user['UserName']
)['AccessKeyMetadata']
for key in keys:
age = datetime.now(key['CreateDate'].tzinfo) - key['CreateDate']
if age.days > 90:
old_keys.append({
'user': user['UserName'],
'key_id': key['AccessKeyId'],
'age_days': age.days
})
return old_keys
def find_overpermissive_policies():
"""Find policies with wildcard actions"""
policies = iam.list_policies(Scope='Local')['Policies']
overpermissive = []
for policy in policies:
version = iam.get_policy_version(
PolicyArn=policy['Arn'],
VersionId=policy['DefaultVersionId']
)
doc = version['PolicyVersion']['Document']
for statement in doc.get('Statement', []):
if statement.get('Action') == '*':
overpermissive.append(policy['PolicyName'])
break
return overpermissive
if __name__ == "__main__":
print("IAM Hardening Report")
print("=" * 50)
print("\nUsers without MFA:")
for user in enforce_mfa():
print(f" - {user}")
print("\nOld access keys (>90 days):")
for key in rotate_old_keys():
print(f" - {key['user']}: {key['age_days']} days")
print("\nOverpermissive policies:")
for policy in find_overpermissive_policies():
print(f" - {policy}")
Example Prompts
- "Review my IAM policies for security issues"
- "Find users without MFA enabled"
- "Create a least privilege policy for S3 access"
- "Identify overly permissive IAM roles"
- "Generate an IAM hardening report"
Best Practices
- Use AWS managed policies when possible
- Implement policy versioning
- Test policies in non-production first
- Document policy purposes
- Regular access reviews (quarterly)
- Use IAM Access Analyzer
- Implement SCPs for organization-wide controls
Kiro CLI Integration
kiro-cli chat "Use aws-iam-best-practices to review my IAM setup"
kiro-cli chat "Create a least privilege policy with aws-iam-best-practices"
Additional Resources
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.