Skip to main content

Overview

This guide provides actionable remediation workflows for common IAM security findings identified by the audit tool. Each workflow includes AWS CLI commands, risk considerations, and rollback procedures.
Always test remediation workflows in non-production environments first. Communicate changes to affected users before taking action.

Pre-Remediation Checklist

Before executing any remediation:
1

Identify the Owner

Determine who owns the IAM user or access key:
aws iam list-user-tags --user-name USERNAME --profile ACCOUNT_PROFILE
Look for Owner, Team, or Email tags.
2

Assess Impact

Check if the credential is actively used:
  • Review last_used_date in the audit report
  • Check CloudTrail for recent API activity
3

Notify Stakeholders

Send advance notice before disabling or deleting credentials. See Communication Templates below.
4

Document the Change

Create a ticket or log entry with:
  • Finding details (username, key ID, account)
  • Planned remediation action
  • Expected completion date

Workflow 1: Enforce MFA for Console Users

Problem

Users with console access but no MFA protection:
  • password_status: Configurada
  • mfa_status: None

Remediation Steps

1

Identify Affected Users

Filter the audit report:
awk -F',' '$4=="Configurada" && $11=="None" {print $2","$3}' iam_audit_report_*.csv | sort -u
Output: account_name,username
2

Require MFA via IAM Policy

Create a policy that denies all actions except MFA setup until MFA is enabled:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyAllExceptMFASetup",
      "Effect": "Deny",
      "NotAction": [
        "iam:CreateVirtualMFADevice",
        "iam:EnableMFADevice",
        "iam:GetUser",
        "iam:ListMFADevices",
        "iam:ListVirtualMFADevices",
        "iam:ResyncMFADevice",
        "sts:GetSessionToken"
      ],
      "Resource": "*",
      "Condition": {
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
        }
      }
    }
  ]
}
3

Attach Policy to Users

Apply the policy:
aws iam put-user-policy \
  --user-name USERNAME \
  --policy-name RequireMFAPolicy \
  --policy-document file://require-mfa.json \
  --profile ACCOUNT_PROFILE
4

Verify Enforcement

User attempts to access AWS Console → Forced to set up MFA before proceeding.
5

Remove Policy After MFA Setup

Once MFA is enabled:
aws iam delete-user-policy \
  --user-name USERNAME \
  --policy-name RequireMFAPolicy \
  --profile ACCOUNT_PROFILE
This approach forces users to set up MFA immediately. Provide clear instructions to avoid support tickets.

Alternative: Organization-Level MFA Enforcement

For AWS Organizations, use a Service Control Policy (SCP):
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyConsoleAccessWithoutMFA",
      "Effect": "Deny",
      "Action": "*",
      "Resource": "*",
      "Condition": {
        "BoolIfExists": {
          "aws:MultiFactorAuthPresent": "false"
        },
        "StringNotEquals": {
          "aws:PrincipalType": "AssumedRole"
        }
      }
    }
  ]
}
Apply to OUs containing production accounts.

Workflow 2: Disable Old Access Keys Safely

Problem

Access keys older than 90 days (or organizational threshold):
  • created_date exceeds age policy
  • status: Active

Remediation Steps

1

Identify Old Keys

Extract keys older than 90 days:
awk -F',' 'NR>1 {print $1","$3","$6","$8}' iam_audit_report_*.csv | \
  awk -F',' '{split($4,a," "); if ((systime()-mktime(gensub(/-/," ","g",a[1])" "a[2]))/86400 > 90) print}'
2

Check Recent Usage

For each key, verify last_used_date in the report. If used within 7 days, coordinate with the owner.
3

Deactivate (Don't Delete) First

IMPORTANT: Deactivate instead of deleting to allow quick rollback:
aws iam update-access-key \
  --user-name USERNAME \
  --access-key-id AKIAIOSFODNN7EXAMPLE \
  --status Inactive \
  --profile ACCOUNT_PROFILE
4

Monitor for Failures

Watch CloudTrail for authentication failures over the next 24-48 hours:
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAIOSFODNN7EXAMPLE \
  --start-time $(date -u -d '1 day ago' +%Y-%m-%dT%H:%M:%S) \
  --profile ACCOUNT_PROFILE
5

Delete After Confirmation

If no issues reported after 48 hours:
aws iam delete-access-key \
  --user-name USERNAME \
  --access-key-id AKIAIOSFODNN7EXAMPLE \
  --profile ACCOUNT_PROFILE
The two-step process (deactivate → delete) gives you a safety net. Reactivating a key is instant; recovering a deleted key is impossible.

Rollback Procedure

If an application breaks after deactivation:
aws iam update-access-key \
  --user-name USERNAME \
  --access-key-id AKIAIOSFODNN7EXAMPLE \
  --status Active \
  --profile ACCOUNT_PROFILE
Then work with the application owner to migrate to IAM roles.

Workflow 3: Migrate from Long-Term to Temporary Credentials

Problem

Applications using long-term access keys instead of temporary credentials:
  • Keys embedded in application code or environment variables
  • Services that could use IAM roles but don’t

Remediation Steps (EC2 Example)

1

Create IAM Role for EC2

aws iam create-role \
  --role-name MyAppEC2Role \
  --assume-role-policy-document '{
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {"Service": "ec2.amazonaws.com"},
      "Action": "sts:AssumeRole"
    }]
  }' \
  --profile ACCOUNT_PROFILE
2

Attach Permissions

Grant the same permissions the access key had:
aws iam attach-role-policy \
  --role-name MyAppEC2Role \
  --policy-arn arn:aws:iam::aws:policy/SERVICE_POLICY \
  --profile ACCOUNT_PROFILE
3

Create Instance Profile

aws iam create-instance-profile \
  --instance-profile-name MyAppEC2Role \
  --profile ACCOUNT_PROFILE

aws iam add-role-to-instance-profile \
  --instance-profile-name MyAppEC2Role \
  --role-name MyAppEC2Role \
  --profile ACCOUNT_PROFILE
4

Attach to EC2 Instance

aws ec2 associate-iam-instance-profile \
  --instance-id i-1234567890abcdef0 \
  --iam-instance-profile Name=MyAppEC2Role \
  --profile ACCOUNT_PROFILE
5

Update Application Code

Remove hardcoded credentials. AWS SDKs automatically use instance profile credentials:
# Before (using access keys)
import boto3
s3 = boto3.client('s3',
  aws_access_key_id='AKIAI...',
  aws_secret_access_key='...')

# After (using IAM role)
import boto3
s3 = boto3.client('s3')  # Automatically uses instance role
6

Test and Deactivate Key

Verify the application works with the IAM role, then deactivate the old access key.

Common Migration Scenarios

Problem: Lambda using access keys stored in environment variables.Solution: Attach an execution role with required permissions:
aws lambda update-function-configuration \
  --function-name MyFunction \
  --role arn:aws:iam::ACCOUNT_ID:role/LambdaExecutionRole \
  --profile ACCOUNT_PROFILE
Remove AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY environment variables.
Problem: ECS containers with access keys in task definitions.Solution: Use task IAM roles:
{
  "family": "my-task",
  "taskRoleArn": "arn:aws:iam::ACCOUNT_ID:role/MyTaskRole",
  "containerDefinitions": [...]
}
Register updated task definition and redeploy services.
Problem: Access keys used to access resources in another account.Solution: Use IAM role assumption:
aws sts assume-role \
  --role-arn arn:aws:iam::TARGET_ACCOUNT:role/CrossAccountRole \
  --role-session-name MySession \
  --profile SOURCE_ACCOUNT_PROFILE
Update application to assume role instead of using static keys.
Problem: Build pipelines using long-term keys.Solution: Use OIDC federation with GitHub Actions, GitLab, or other providers:
# GitHub Actions example
- name: Configure AWS Credentials
  uses: aws-actions/configure-aws-credentials@v2
  with:
    role-to-assume: arn:aws:iam::ACCOUNT_ID:role/GitHubActionsRole
    aws-region: us-east-1
No access keys required.

Workflow 4: Delete Unused IAM Users

Problem

Users with no recent activity (zombie accounts):
  • password_last_used is older than 90 days (or never used)
  • last_used_date shows no key usage in 90 days

Remediation Steps

1

Verify User is Inactive

Cross-reference with HR/IT to confirm user has left the organization or no longer needs access.
2

Audit User Permissions

Check what the user had access to:
aws iam list-attached-user-policies --user-name USERNAME --profile ACCOUNT_PROFILE
aws iam list-user-policies --user-name USERNAME --profile ACCOUNT_PROFILE
aws iam list-groups-for-user --user-name USERNAME --profile ACCOUNT_PROFILE
3

Delete Access Keys First

aws iam list-access-keys --user-name USERNAME --profile ACCOUNT_PROFILE
aws iam delete-access-key --user-name USERNAME --access-key-id AKIAI... --profile ACCOUNT_PROFILE
4

Delete Login Profile

Remove console access:
aws iam delete-login-profile --user-name USERNAME --profile ACCOUNT_PROFILE
5

Remove from Groups

aws iam remove-user-from-group --user-name USERNAME --group-name GROUPNAME --profile ACCOUNT_PROFILE
6

Detach Policies

aws iam detach-user-policy --user-name USERNAME --policy-arn POLICY_ARN --profile ACCOUNT_PROFILE
7

Delete Inline Policies

aws iam delete-user-policy --user-name USERNAME --policy-name POLICY_NAME --profile ACCOUNT_PROFILE
8

Delete the User

aws iam delete-user --user-name USERNAME --profile ACCOUNT_PROFILE
IAM user deletion is permanent. Ensure you have proper approval before executing this workflow.

Workflow 5: Rotate Access Keys Without Downtime

Problem

Keys need rotation but application requires 24/7 availability.

Remediation Steps (Dual-Key Strategy)

1

Create Second Access Key

AWS allows two keys per user:
aws iam create-access-key --user-name USERNAME --profile ACCOUNT_PROFILE
Save the new AccessKeyId and SecretAccessKey.
2

Update Application Configuration

Deploy the new key to your application (environment variables, secrets manager, etc.).
3

Test with New Key

Verify the application works with the new credentials.
4

Deactivate Old Key

aws iam update-access-key \
  --user-name USERNAME \
  --access-key-id OLD_KEY_ID \
  --status Inactive \
  --profile ACCOUNT_PROFILE
5

Monitor for Errors

Wait 48 hours. If no issues arise, delete the old key:
aws iam delete-access-key \
  --user-name USERNAME \
  --access-key-id OLD_KEY_ID \
  --profile ACCOUNT_PROFILE
This workflow eliminates downtime by ensuring the new key works before removing the old one.

Using CloudTrail to Track Remediation

Verify Remediation Actions

After executing remediation, confirm the actions appear in CloudTrail:
aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=DeleteAccessKey \
  --start-time $(date -u -d '1 day ago' +%Y-%m-%dT%H:%M:%S) \
  --profile ACCOUNT_PROFILE \
  --query 'Events[*].CloudTrailEvent' \
  --output text | jq .

Track Progress Over Time

Re-run IAM Audit monthly and compare CloudTrail reports:
# Count DeleteAccessKey events this month
grep "DeleteAccessKey" cloudtrail_events_*.csv | wc -l

# Count CreateAccessKey events (should be decreasing)
grep "CreateAccessKey" cloudtrail_events_*.csv | wc -l
If CreateAccessKey events are increasing, investigate why new long-term credentials are being created.

Communication Templates

Template 1: MFA Enforcement Notification

Subject: ACTION REQUIRED: Enable MFA for AWS Console Access

Hi [Username],

Our security audit identified that your AWS IAM user account has console access without Multi-Factor Authentication (MFA) enabled.

**Action Required:**
Enable MFA within 7 days to maintain console access. After this period, access will be restricted until MFA is configured.

**How to Enable MFA:**
1. Log in to AWS Console: https://console.aws.amazon.com/
2. Navigate to IAM → Users → [Your Username]
3. Security Credentials tab → Assign MFA device
4. Follow the setup wizard (recommended: virtual MFA via mobile app)

**Why This Matters:**
MFA protects against password compromise and is required by our security policy.

Questions? Reply to this email or contact [Security Team Contact].

Thank you,
[Your Security Team]

Template 2: Access Key Deactivation Notice

Subject: AWS Access Key Scheduled for Deactivation

Hi [Username],

Our IAM audit identified an access key associated with your account that was created on [DATE] — over [X] days ago.

**Access Key ID:** AKIAI***EXAMPLE (last 7 characters hidden)
**Last Used:** [DATE]
**Service:** [SERVICE_NAME]

**Action:**
This key will be deactivated on [DATE + 7 DAYS]. If you still require this key:
1. Reply to this email with justification
2. We'll work with you to migrate to temporary credentials (IAM roles)

**No Action Required If:**
- You no longer use this key
- You've already migrated to IAM roles

Questions? Contact [Security Team Contact].

Thank you,
[Your Security Team]

Template 3: User Deletion Notice

Subject: AWS IAM User Account Scheduled for Deletion

Hi [Manager/Team Lead],

Our IAM audit identified an inactive user account:
- **Username:** [USERNAME]
- **Account:** [ACCOUNT_NAME]
- **Last Console Login:** [DATE or "Never"]
- **Last Access Key Usage:** [DATE or "Never"]

**Reason for Deletion:**
The account has been inactive for over 90 days and appears to belong to a former team member.

**Action:**
This account will be deleted on [DATE + 14 DAYS]. If this account is still needed, please reply immediately with:
- Current owner's name and contact
- Business justification for continued access

**No Response = Account Deleted**

Thank you,
[Your Security Team]

Automation Opportunities

Scheduled Audits with Lambda

Run IAM Audit monthly via Lambda:
import boto3
import subprocess
from datetime import datetime

def lambda_handler(event, context):
    # Clone your iam-audit repo or package it with Lambda
    # Run the audit
    result = subprocess.run(
        ['python', 'iam_audit.py', '--profile', 'mgmt', '--role', 'AuditRole'],
        capture_output=True
    )
    
    # Upload CSV reports to S3
    s3 = boto3.client('s3')
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    s3.upload_file(
        f'iam_audit_report_{timestamp}.csv',
        'my-audit-bucket',
        f'reports/iam_audit_report_{timestamp}.csv'
    )
    
    return {'statusCode': 200, 'body': 'Audit complete'}
Schedule with EventBridge: cron(0 9 1 * ? *) (monthly on the 1st at 9 AM UTC)

Automated Key Rotation

Create a Lambda function that:
  1. Reads the latest audit CSV from S3
  2. Identifies keys older than 90 days
  3. Sends SNS notifications to key owners
  4. Optionally auto-deactivates keys older than 180 days
Trigger weekly via EventBridge.

Security Hub Custom Findings

import boto3
import csv
from datetime import datetime

def send_to_security_hub(findings):
    securityhub = boto3.client('securityhub')
    
    for finding in findings:
        if finding['mfa_status'] == 'None' and finding['password_status'] == 'Configurada':
            securityhub.batch_import_findings(
                Findings=[{
                    'SchemaVersion': '2018-10-08',
                    'Id': f"iam-audit/{finding['account_id']}/{finding['username']}",
                    'ProductArn': f"arn:aws:securityhub:{region}:{account_id}:product/{account_id}/default",
                    'GeneratorId': 'iam-audit-tool',
                    'AwsAccountId': finding['account_id'],
                    'Types': ['Software and Configuration Checks/AWS Security Best Practices'],
                    'CreatedAt': datetime.now().isoformat() + 'Z',
                    'UpdatedAt': datetime.now().isoformat() + 'Z',
                    'Severity': {'Label': 'HIGH'},
                    'Title': f"IAM user {finding['username']} has console access without MFA",
                    'Description': f"User {finding['username']} can access AWS Console without multi-factor authentication.",
                    'Resources': [{
                        'Type': 'AwsIamUser',
                        'Id': f"arn:aws:iam::{finding['account_id']}:user/{finding['username']}",
                        'Region': region
                    }]
                }]
            )

Measuring Remediation Success

Key Metrics to Track

MetricTargetMeasurement Method
MFA Coverage100% for console users(Users with MFA / Total console users) × 100
Key AgeNo keys older than 90 daysCount keys where created_date exceeds threshold
Unused KeysZero keys never usedCount keys with last_used_date: Nunca utilizada
User Cleanup RateAll inactive users removed within 30 daysTrack DeleteUser events in CloudTrail
Key Rotation VelocityDecrease in average key age month-over-monthCalculate median key age from audit reports

Monthly Remediation Dashboard

Create a simple tracking spreadsheet:
Month       | Total Keys | Keys >90d | Keys >1yr | MFA Coverage | Inactive Users
2026-01     | 245        | 87        | 12        | 65%          | 23
2026-02     | 230        | 62        | 8         | 78%          | 15
2026-03     | 198        | 34        | 2         | 92%          | 6
Visualize trends to show security improvements to leadership.

Troubleshooting Common Issues

Problem: IAM user deletion fails because policies are still attached.Solution: Run this script to clean up all attachments:
#!/bin/bash
USERNAME=$1
PROFILE=$2

# Detach managed policies
aws iam list-attached-user-policies --user-name $USERNAME --profile $PROFILE \
  --query 'AttachedPolicies[*].PolicyArn' --output text | \
  xargs -n1 aws iam detach-user-policy --user-name $USERNAME --profile $PROFILE --policy-arn

# Delete inline policies
aws iam list-user-policies --user-name $USERNAME --profile $PROFILE \
  --query 'PolicyNames[*]' --output text | \
  xargs -n1 aws iam delete-user-policy --user-name $USERNAME --profile $PROFILE --policy-name

# Remove from groups
aws iam list-groups-for-user --user-name $USERNAME --profile $PROFILE \
  --query 'Groups[*].GroupName' --output text | \
  xargs -n1 aws iam remove-user-from-group --user-name $USERNAME --profile $PROFILE --group-name

# Delete access keys
aws iam list-access-keys --user-name $USERNAME --profile $PROFILE \
  --query 'AccessKeyMetadata[*].AccessKeyId' --output text | \
  xargs -n1 aws iam delete-access-key --user-name $USERNAME --profile $PROFILE --access-key-id

# Delete login profile
aws iam delete-login-profile --user-name $USERNAME --profile $PROFILE 2>/dev/null

# Finally delete user
aws iam delete-user --user-name $USERNAME --profile $PROFILE
Problem: Can’t assume audit role in child accounts.Solution: Verify:
  1. Trust relationship in child account role allows management account:
    {
      "Version": "2012-10-17",
      "Statement": [{
        "Effect": "Allow",
        "Principal": {"AWS": "arn:aws:iam::MGMT_ACCOUNT_ID:root"},
        "Action": "sts:AssumeRole"
      }]
    }
    
  2. Management account has sts:AssumeRole permission for that role ARN.
Problem: Critical service stops working.Solution:
  1. Immediately reactivate the key (see Workflow 2 Rollback)
  2. Identify the application using CloudTrail
  3. Schedule a maintenance window to migrate to IAM roles
  4. Use Workflow 3 to implement temporary credentials

Next Steps

Interpreting Results

Understand how to analyze audit findings

Security Maturity Model

Track your AWS SMM progress

AWS IAM Best Practices

Official AWS security recommendations

Temporary Credentials Guide

Deep dive into AWS STS and temporary credentials

Build docs developers (and LLMs) love