Install any skill in seconds. Free to start, no credit card required.
Get Started Free →This skill guides practitioners through hardening AWS Identity and Access Management configurations to enforce least privilege access across cloud accounts. It covers IAM policy scoping, permission boundaries, Access Analyzer integration, and credential rotation strategies to reduce the blast radius of compromised identities.
.claude/skills/securing-aws-iam-permissions/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-04 | ✗→✓ | ▲ Improved | — | — |
| case-03 | ✗→✓ | ▲ Improved | — | — |
| case-13 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✗→✓ | ▲ Improved | — | — |
| case-01 | ✗→✓ | ▲ Improved | — | — |
Do not use for Azure AD or Google Cloud IAM configurations, application-level authorization logic, or federated identity provider setup (see managing-cloud-identity-with-okta).
Generate a comprehensive inventory of all IAM users, roles, groups, and attached policies using the AWS CLI and IAM credential reports. Identify accounts with console access, programmatic access keys, and their last-used timestamps.
bash# Generate IAM credential report aws iam generate-credential-report aws iam get-credential-report --query 'Content' --output text | base64 -d > iam-report.csv # List all IAM roles and their attached policies aws iam list-roles --query 'Roles[*].[RoleName,Arn,CreateDate]' --output table # Find users with access keys older than 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[?CreateDate<='$(date -d '-90 days' +%Y-%m-%d)'].[UserName,AccessKeyId,Status,CreateDate]" \ --output table done
Activate IAM Access Analyzer at the organization or account level to identify resources shared externally and generate least-privilege policy recommendations based on CloudTrail activity.
bash# Create an Access Analyzer for the account aws accessanalyzer create-analyzer \ --analyzer-name account-analyzer \ --type ACCOUNT # List active findings for external access aws accessanalyzer list-findings \ --analyzer-arn arn:aws:access-analyzer:us-east-1:123456789012:analyzer/account-analyzer \ --filter '{"status": {"eq": ["ACTIVE"]}}' # Generate a policy based on CloudTrail activity for a specific role aws accessanalyzer start-policy-generation \ --policy-generation-details '{ "principalArn": "arn:aws:iam::123456789012:role/AppRole", "cloudTrailDetails": { "trailArn": "arn:aws:cloudtrail:us-east-1:123456789012:trail/management-trail", "startTime": "2025-01-01T00:00:00Z", "endTime": "2025-03-01T00:00:00Z" } }'
Replace wildcard resource ARNs with specific resource identifiers. Add IAM policy conditions for MFA enforcement, source IP restrictions, and time-based access windows.
json{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowS3ReadSpecificBucket", "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::production-data-bucket", "arn:aws:s3:::production-data-bucket/*" ], "Condition": { "Bool": {"aws:MultiFactorAuthPresent": "true"}, "IpAddress": {"aws:SourceIp": "10.0.0.0/8"}, "DateGreaterThan": {"aws:CurrentTime": "2025-01-01T00:00:00Z"} } } ] }
Attach permission boundaries to IAM roles and users to define the maximum scope of permissions an entity can receive, preventing privilege escalation even if an administrator attaches an overly permissive policy.
bash# Create a permission boundary policy aws iam create-policy \ --policy-name DeveloperPermissionBoundary \ --policy-document file://developer-boundary.json # Attach the boundary to an IAM role aws iam put-role-permissions-boundary \ --role-name DeveloperRole \ --permissions-boundary "arn:aws:iam::123456789012:policy/DeveloperPermissionBoundary"
json{ "Version": "2012-10-17", "Statement": [ { "Sid": "AllowCommonServices", "Effect": "Allow", "Action": [ "s3:*", "dynamodb:*", "lambda:*", "logs:*", "cloudwatch:*" ], "Resource": "*" }, { "Sid": "DenyIAMChanges", "Effect": "Deny", "Action": [ "iam:CreateUser", "iam:DeleteUser", "iam:CreateRole", "iam:DeleteRole", "iam:AttachRolePolicy", "iam:PutRolePermissionsBoundary" ], "Resource": "*" } ] }
Require MFA for all human users accessing the AWS console and CLI. Migrate workloads from IAM user access keys to IAM roles with temporary credentials via STS AssumeRole.
bash# Enforce MFA via SCP at the organization level aws organizations create-policy \ --name RequireMFA \ --type SERVICE_CONTROL_POLICY \ --content '{ "Version": "2012-10-17", "Statement": [ { "Sid": "DenyAllExceptMFA", "Effect": "Deny", "NotAction": [ "iam:CreateVirtualMFADevice", "iam:EnableMFADevice", "iam:ListMFADevices", "iam:ResyncMFADevice", "sts:GetSessionToken" ], "Resource": "*", "Condition": { "BoolIfExists": {"aws:MultiFactorAuthPresent": "false"} } } ] }' # Deactivate unused access keys aws iam update-access-key --user-name old-user --access-key-id AKIAEXAMPLE --status Inactive
Deploy AWS Config rules and Security Hub controls to continuously evaluate IAM posture. Set up EventBridge rules to alert on high-risk IAM changes such as new root access key creation or policy modifications.
bash# Enable AWS Config rule for IAM password policy aws configservice put-config-rule \ --config-rule '{ "ConfigRuleName": "iam-password-policy", "Source": { "Owner": "AWS", "SourceIdentifier": "IAM_PASSWORD_POLICY" }, "InputParameters": "{\"RequireUppercaseCharacters\":\"true\",\"RequireLowercaseCharacters\":\"true\",\"RequireSymbols\":\"true\",\"RequireNumbers\":\"true\",\"MinimumPasswordLength\":\"14\",\"MaxPasswordAge\":\"90\"}" }' # EventBridge rule to detect root account usage aws events put-rule \ --name DetectRootUsage \ --event-pattern '{ "detail-type": ["AWS API Call via CloudTrail"], "detail": { "userIdentity": {"type": ["Root"]} } }'
| Term | Definition | |------|------------| | Least Privilege | Granting only the minimum permissions required for an identity to perform its function | | Permission Boundary | An advanced IAM feature that sets the maximum permissions an entity can have, regardless of attached policies | | IAM Access Analyzer | AWS service that uses automated reasoning to identify resources shared externally and generate least-privilege policies from CloudTrail activity | | Service Control Policy (SCP) | Organization-level policy that sets permission guardrails across all accounts in an AWS Organization | | Assume Role | STS operation that returns temporary security credentials for cross-account or service-to-service access | | Credential Report | AWS-generated CSV listing all IAM users, their access keys, MFA status, and last activity timestamps | | Policy Condition | Constraints in IAM policies that restrict when and how permissions apply, such as MFA requirements or IP ranges | | Identity Federation | Allowing external identity providers to grant temporary AWS access without creating IAM users |
Context: A startup attached the AWS-managed AdministratorAccess policy to all developer roles for speed during early development. A security audit reveals 15 roles with full account access while developers only use S3, Lambda, and DynamoDB.
Approach:
Pitfalls: Replacing policies without a parallel testing period causes service disruptions. Forgetting to scope Lambda:InvokeFunction to specific function ARNs leaves lateral movement paths open.
Context: An access key is found in a public GitHub repository. The key belongs to an IAM user with S3 and EC2 permissions across three AWS accounts.
Approach:
aws iam update-access-key --status InactivePitfalls: Deleting the key before deactivating it prevents forensic analysis of which services relied on it. Failing to check all three accounts for unauthorized activity leaves potential backdoors undetected.
IAM Security Assessment Report
==============================
Account ID: 123456789012
Assessment Date: 2025-02-23
Analyzer: IAM Access Analyzer + Prowler v4.3
CRITICAL FINDINGS:
[C-001] Root account has active access keys
- Resource: arn:aws:iam::123456789012:root
- Remediation: Delete root access keys, enable MFA on root
- CIS Benchmark: 1.4 (Ensure no root account access key exists)
[C-002] IAM user 'deploy-bot' has AdministratorAccess with no MFA
- Resource: arn:aws:iam::123456789012:user/deploy-bot
- Last Activity: 2025-02-20
- Remediation: Replace with IAM role, enforce MFA condition
HIGH FINDINGS:
[H-001] 3 IAM policies use wildcard Resource "*" with sensitive actions
- Policies: DevPolicy, CIPolicy, LegacyAdminPolicy
- Remediation: Scope resources to specific ARNs using Access Analyzer
[H-002] 7 access keys older than 90 days detected
- Users: svc-backup, svc-monitoring, dev-alice, dev-bob, ...
- Remediation: Rotate keys, migrate to role-based access
SUMMARY:
Total Findings: 14
Critical: 2 | High: 4 | Medium: 5 | Low: 3
Compliance Score: 62% (CIS AWS Foundations Benchmark v3.0)| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.