Security

How to Rotate AWS IAM Access Keys Automatically with Lambda

Updated By Zak Kann

Key takeaways

  • IAM access keys older than 90 days create security vulnerabilities—AWS recommends rotation but doesn't enforce it automatically
  • Automated rotation using Lambda + EventBridge scans all IAM users daily, identifies keys over threshold (60/90 days), and sends email notifications via SES
  • Grace period workflow (warning at 60 days, mandatory rotation at 90 days) gives users time to update applications before forced rotation
  • Secrets Manager integration stores rotated keys and enables automatic application retrieval without manual updates
  • Complete audit trail via CloudWatch Logs and DynamoDB tracking ensures compliance with SOC 2, ISO 27001, and PCI-DSS requirements

The IAM Access Key Problem

Your security audit just flagged 47 IAM users with access keys older than 90 days. Three are over 2 years old. One belongs to an engineer who left 6 months ago.

The risk:

  • Compromised keys = full AWS account access
  • No rotation = extended exposure window
  • Shared keys = impossible to audit who did what
  • Forgotten keys = zombie credentials

Industry benchmarks:

  • AWS recommendation: Rotate every 90 days
  • SOC 2 requirement: Access key rotation policy
  • PCI-DSS: Change authentication credentials every 90 days
  • ISO 27001: Periodic credential review

Manual rotation doesn't scale:

  • Send email to 47 users
  • Track who rotated, who didn't
  • Follow up with non-compliant users
  • Disable old keys manually
  • Repeat every 90 days

Solution: Automated key rotation with Lambda

Architecture Overview

┌─────────────────────────────────────────────────────────────────┐
│                     EventBridge Schedule                        │
│                  (Daily at 9 AM UTC)                           │
└────────────┬────────────────────────────────────────────────────┘
             │
             ▼
    ┌─────────────────┐
    │ Lambda Function │
    │ Key Rotation    │
    │ Orchestrator    │
    └────────┬────────┘
             │
             ├─────────────────────┬──────────────────────┬───────────────────┐
             │                     │                      │                   │
             ▼                     ▼                      ▼                   ▼
    ┌─────────────┐      ┌──────────────┐      ┌─────────────┐    ┌──────────────┐
    │ IAM API     │      │ SES          │      │ Secrets     │    │ DynamoDB     │
    │ List Users  │      │ Send Email   │      │ Manager     │    │ Audit Log    │
    │ List Keys   │      │ Notifications│      │ Store Keys  │    │ Tracking     │
    └─────────────┘      └──────────────┘      └─────────────┘    └──────────────┘
             │                     │                      │                   │
             └─────────────────────┴──────────────────────┴───────────────────┘
                                          │
                                          ▼
                                  CloudWatch Logs
                                  (Audit Trail)

Implementation: Step-by-Step

Step 1: Lambda Function - Key Age Detection

import { IAM, SES } from 'aws-sdk';
import { Handler } from 'aws-lambda';
 
const iam = new IAM();
const ses = new SES();
 
interface KeyAgeReport {
  userName: string;
  accessKeyId: string;
  ageInDays: number;
  status: 'Active' | 'Inactive';
  lastUsed?: Date;
}
 
export const handler: Handler = async (event) => {
  console.log('Starting IAM key rotation check');
 
  const users = await iam.listUsers().promise();
  const agingKeys: KeyAgeReport[] = [];
 
  for (const user of users.Users || []) {
    const accessKeys = await iam.listAccessKeys({
      UserName: user.UserName
    }).promise();
 
    for (const key of accessKeys.AccessKeyMetadata || []) {
      if (!key.CreateDate || key.Status !== 'Active') continue;
 
      const ageInDays = calculateAgeInDays(key.CreateDate);
 
      // Check last usage
      const lastUsed = await iam.getAccessKeyLastUsed({
        AccessKeyId: key.AccessKeyId!
      }).promise();
 
      const report: KeyAgeReport = {
        userName: user.UserName!,
        accessKeyId: key.AccessKeyId!,
        ageInDays,
        status: key.Status as 'Active',
        lastUsed: lastUsed.AccessKeyLastUsed?.LastUsedDate
      };
 
      // Threshold: 60 days = warning, 90 days = rotate
      if (ageInDays >= 60) {
        agingKeys.push(report);
      }
    }
  }
 
  // Process aging keys
  await processAgingKeys(agingKeys);
 
  return {
    statusCode: 200,
    body: JSON.stringify({
      totalUsersScanned: users.Users?.length || 0,
      agingKeysFound: agingKeys.length,
      timestamp: new Date().toISOString()
    })
  };
};
 
function calculateAgeInDays(createDate: Date): number {
  const now = new Date();
  const diffMs = now.getTime() - createDate.getTime();
  return Math.floor(diffMs / (1000 * 60 * 60 * 24));
}
 
async function processAgingKeys(keys: KeyAgeReport[]): Promise<void> {
  for (const key of keys) {
    if (key.ageInDays >= 90) {
      // Force rotation after 90 days
      await rotateKey(key);
    } else if (key.ageInDays >= 60) {
      // Send warning email
      await sendWarningEmail(key);
    }
  }
}

Step 2: Email Notification System

async function sendWarningEmail(key: KeyAgeReport): Promise<void> {
  const user = await iam.getUser({ UserName: key.userName }).promise();
  const email = user.User?.Tags?.find(t => t.Key === 'Email')?.Value;
 
  if (!email) {
    console.log(`No email tag found for user: ${key.userName}`);
    return;
  }
 
  const daysUntilRotation = 90 - key.ageInDays;
 
  const emailParams = {
    Source: process.env.SENDER_EMAIL!,
    Destination: {
      ToAddresses: [email],
      CcAddresses: [process.env.SECURITY_TEAM_EMAIL!]
    },
    Message: {
      Subject: {
        Data: `⚠️ AWS Access Key Rotation Required (${daysUntilRotation} days remaining)`
      },
      Body: {
        Html: {
          Data: `
            <html>
              <body>
                <h2>AWS Access Key Rotation Notice</h2>
                <p>Your AWS access key requires rotation:</p>
 
                <table style="border-collapse: collapse; margin: 20px 0;">
                  <tr>
                    <td style="padding: 8px; border: 1px solid #ddd;"><strong>User:</strong></td>
                    <td style="padding: 8px; border: 1px solid #ddd;">${key.userName}</td>
                  </tr>
                  <tr>
                    <td style="padding: 8px; border: 1px solid #ddd;"><strong>Access Key ID:</strong></td>
                    <td style="padding: 8px; border: 1px solid #ddd;">${key.accessKeyId}</td>
                  </tr>
                  <tr>
                    <td style="padding: 8px; border: 1px solid #ddd;"><strong>Age:</strong></td>
                    <td style="padding: 8px; border: 1px solid #ddd;">${key.ageInDays} days</td>
                  </tr>
                  <tr>
                    <td style="padding: 8px; border: 1px solid #ddd;"><strong>Last Used:</strong></td>
                    <td style="padding: 8px; border: 1px solid #ddd;">${key.lastUsed?.toISOString() || 'Never'}</td>
                  </tr>
                  <tr style="background-color: #fff3cd;">
                    <td style="padding: 8px; border: 1px solid #ddd;"><strong>Days Until Forced Rotation:</strong></td>
                    <td style="padding: 8px; border: 1px solid #ddd;"><strong>${daysUntilRotation} days</strong></td>
                  </tr>
                </table>
 
                <h3>Action Required:</h3>
                <ol>
                  <li>Create a new access key in the AWS Console</li>
                  <li>Update your applications to use the new key</li>
                  <li>Test thoroughly</li>
                  <li>Delete the old key: <code>${key.accessKeyId}</code></li>
                </ol>
 
                <h3>Consequences of Inaction:</h3>
                <ul>
                  <li>In ${daysUntilRotation} days, this key will be <strong>automatically rotated</strong></li>
                  <li>Applications using the old key will fail authentication</li>
                  <li>You will receive the new key via Secrets Manager</li>
                </ul>
 
                <p><a href="https://console.aws.amazon.com/iam/home#/users/${key.userName}?section=security_credentials">View in AWS Console →</a></p>
 
                <hr />
                <p style="color: #666; font-size: 12px;">
                  This is an automated message from AWS Security Automation.<br />
                  Questions? Contact security@company.com
                </p>
              </body>
            </html>
          `
        }
      }
    }
  };
 
  await ses.sendEmail(emailParams).promise();
  console.log(`Warning email sent to ${email} for user ${key.userName}`);
}

Step 3: Automated Key Rotation

import { SecretsManager } from 'aws-sdk';
 
const secretsManager = new SecretsManager();
 
async function rotateKey(key: KeyAgeReport): Promise<void> {
  console.log(`Force rotating key for user: ${key.userName}`);
 
  try {
    // 1. Create new access key
    const newKey = await iam.createAccessKey({
      UserName: key.userName
    }).promise();
 
    if (!newKey.AccessKey) {
      throw new Error('Failed to create new access key');
    }
 
    // 2. Store new key in Secrets Manager
    const secretName = `iam/${key.userName}/access-key`;
 
    try {
      await secretsManager.createSecret({
        Name: secretName,
        SecretString: JSON.stringify({
          AccessKeyId: newKey.AccessKey.AccessKeyId,
          SecretAccessKey: newKey.AccessKey.SecretAccessKey,
          CreatedDate: newKey.AccessKey.CreateDate?.toISOString(),
          RotatedFrom: key.accessKeyId
        }),
        Tags: [
          { Key: 'User', Value: key.userName },
          { Key: 'RotationDate', Value: new Date().toISOString() }
        ]
      }).promise();
    } catch (error: any) {
      if (error.code === 'ResourceExistsException') {
        // Update existing secret
        await secretsManager.putSecretValue({
          SecretId: secretName,
          SecretString: JSON.stringify({
            AccessKeyId: newKey.AccessKey.AccessKeyId,
            SecretAccessKey: newKey.AccessKey.SecretAccessKey,
            CreatedDate: newKey.AccessKey.CreateDate?.toISOString(),
            RotatedFrom: key.accessKeyId
          })
        }).promise();
      } else {
        throw error;
      }
    }
 
    // 3. Deactivate old key (don't delete immediately)
    await iam.updateAccessKey({
      UserName: key.userName,
      AccessKeyId: key.accessKeyId,
      Status: 'Inactive'
    }).promise();
 
    // 4. Send notification with new key location
    await sendRotationNotification(key, newKey.AccessKey.AccessKeyId!, secretName);
 
    // 5. Log to DynamoDB for audit trail
    await logRotation(key, newKey.AccessKey.AccessKeyId!);
 
    console.log(`Successfully rotated key ${key.accessKeyId} → ${newKey.AccessKey.AccessKeyId}`);
  } catch (error) {
    console.error(`Failed to rotate key for ${key.userName}:`, error);
 
    // Send alert to security team
    await sendErrorAlert(key, error);
    throw error;
  }
}
 
async function sendRotationNotification(
  oldKey: KeyAgeReport,
  newKeyId: string,
  secretName: string
): Promise<void> {
  const user = await iam.getUser({ UserName: oldKey.userName }).promise();
  const email = user.User?.Tags?.find(t => t.Key === 'Email')?.Value;
 
  if (!email) return;
 
  const emailParams = {
    Source: process.env.SENDER_EMAIL!,
    Destination: {
      ToAddresses: [email],
      CcAddresses: [process.env.SECURITY_TEAM_EMAIL!]
    },
    Message: {
      Subject: {
        Data: `🔄 AWS Access Key Has Been Rotated`
      },
      Body: {
        Html: {
          Data: `
            <html>
              <body>
                <h2>Access Key Rotation Complete</h2>
                <p>Your AWS access key has been automatically rotated due to age (${oldKey.ageInDays} days).</p>
 
                <h3>Old Key (DEACTIVATED):</h3>
                <pre style="background: #f5f5f5; padding: 10px;">${oldKey.accessKeyId}</pre>
 
                <h3>New Key:</h3>
                <pre style="background: #d4edda; padding: 10px;">${newKeyId}</pre>
 
                <h3>Retrieve New Credentials:</h3>
                <p>The new access key and secret are stored in AWS Secrets Manager:</p>
                <pre style="background: #f5f5f5; padding: 10px;">
aws secretsmanager get-secret-value \\
  --secret-id ${secretName} \\
  --query SecretString \\
  --output text | jq -r</pre>
 
                <h3>Update Your Applications:</h3>
                <ol>
                  <li>Retrieve the new credentials from Secrets Manager</li>
                  <li>Update your application configuration</li>
                  <li>Test thoroughly</li>
                  <li>The old key will be deleted in 7 days</li>
                </ol>
 
                <h3>Automated Retrieval (Recommended):</h3>
                <p>Configure your applications to fetch credentials from Secrets Manager on startup:</p>
                <pre style="background: #f5f5f5; padding: 10px;">
import { SecretsManager } from 'aws-sdk';
 
const secretsManager = new SecretsManager();
const secret = await secretsManager.getSecretValue({
  SecretId: '${secretName}'
}).promise();
 
const credentials = JSON.parse(secret.SecretString!);
// Use credentials.AccessKeyId and credentials.SecretAccessKey
                </pre>
 
                <p><strong>Questions?</strong> Contact security@company.com</p>
              </body>
            </html>
          `
        }
      }
    }
  };
 
  await ses.sendEmail(emailParams).promise();
}

Step 4: Audit Trail with DynamoDB

import { DynamoDB } from 'aws-sdk';
 
const dynamodb = new DynamoDB.DocumentClient();
 
async function logRotation(oldKey: KeyAgeReport, newKeyId: string): Promise<void> {
  await dynamodb.put({
    TableName: process.env.AUDIT_TABLE_NAME!,
    Item: {
      pk: `USER#${oldKey.userName}`,
      sk: `ROTATION#${new Date().toISOString()}`,
      oldKeyId: oldKey.accessKeyId,
      newKeyId: newKeyId,
      keyAge: oldKey.ageInDays,
      rotationType: 'automatic',
      rotationReason: 'exceeded_90_day_threshold',
      timestamp: new Date().toISOString(),
      lastUsed: oldKey.lastUsed?.toISOString() || null
    }
  }).promise();
}

DynamoDB table schema:

resource "aws_dynamodb_table" "key_rotation_audit" {
  name         = "iam-key-rotation-audit"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "pk"
  range_key    = "sk"
 
  attribute {
    name = "pk"
    type = "S"
  }
 
  attribute {
    name = "sk"
    type = "S"
  }
 
  # Enable Point-in-Time Recovery for compliance
  point_in_time_recovery {
    enabled = true
  }
 
  # Enable server-side encryption
  server_side_encryption {
    enabled = true
  }
 
  tags = {
    Purpose = "IAM Key Rotation Audit Trail"
  }
}

Step 5: Terraform Infrastructure

# Lambda function
resource "aws_lambda_function" "key_rotation" {
  filename      = "key-rotation.zip"
  function_name = "iam-key-rotation"
  role          = aws_iam_role.lambda.arn
  handler       = "index.handler"
  runtime       = "nodejs18.x"
  timeout       = 300
 
  environment {
    variables = {
      SENDER_EMAIL         = var.sender_email
      SECURITY_TEAM_EMAIL  = var.security_team_email
      AUDIT_TABLE_NAME     = aws_dynamodb_table.key_rotation_audit.name
    }
  }
}
 
# IAM role for Lambda
resource "aws_iam_role" "lambda" {
  name = "iam-key-rotation-lambda-role"
 
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
        Action = "sts:AssumeRole"
      }
    ]
  })
}
 
resource "aws_iam_role_policy" "lambda" {
  role = aws_iam_role.lambda.id
 
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "iam:ListUsers",
          "iam:ListAccessKeys",
          "iam:GetUser",
          "iam:GetAccessKeyLastUsed",
          "iam:CreateAccessKey",
          "iam:UpdateAccessKey",
          "iam:DeleteAccessKey"
        ]
        Resource = "*"
      },
      {
        Effect = "Allow"
        Action = [
          "secretsmanager:CreateSecret",
          "secretsmanager:PutSecretValue",
          "secretsmanager:GetSecretValue"
        ]
        Resource = "arn:aws:secretsmanager:${var.aws_region}:${data.aws_caller_identity.current.account_id}:secret:iam/*"
      },
      {
        Effect = "Allow"
        Action = [
          "ses:SendEmail",
          "ses:SendRawEmail"
        ]
        Resource = "*"
      },
      {
        Effect = "Allow"
        Action = [
          "dynamodb:PutItem",
          "dynamodb:GetItem",
          "dynamodb:Query"
        ]
        Resource = aws_dynamodb_table.key_rotation_audit.arn
      },
      {
        Effect = "Allow"
        Action = [
          "logs:CreateLogGroup",
          "logs:CreateLogStream",
          "logs:PutLogEvents"
        ]
        Resource = "*"
      }
    ]
  })
}
 
# EventBridge schedule - daily at 9 AM UTC
resource "aws_cloudwatch_event_rule" "daily" {
  name                = "iam-key-rotation-daily"
  schedule_expression = "cron(0 9 * * ? *)"
}
 
resource "aws_cloudwatch_event_target" "lambda" {
  rule      = aws_cloudwatch_event_rule.daily.name
  target_id = "lambda"
  arn       = aws_lambda_function.key_rotation.arn
}
 
resource "aws_lambda_permission" "eventbridge" {
  statement_id  = "AllowExecutionFromEventBridge"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.key_rotation.function_name
  principal     = "events.amazonaws.com"
  source_arn    = aws_cloudwatch_event_rule.daily.arn
}
 
# SES email identity
resource "aws_ses_email_identity" "sender" {
  email = var.sender_email
}

Advanced Feature: Graceful Old Key Deletion

Don't delete old keys immediately—give users 7 days to update applications:

async function scheduleKeyDeletion(keyId: string, userName: string): Promise<void> {
  // Store deletion timestamp in DynamoDB
  await dynamodb.put({
    TableName: process.env.AUDIT_TABLE_NAME!,
    Item: {
      pk: `DELETION#${keyId}`,
      sk: `SCHEDULED`,
      userName: userName,
      keyId: keyId,
      scheduledFor: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
      status: 'PENDING'
    }
  }).promise();
}
 
// Separate Lambda function triggered daily to delete expired keys
export const deleteExpiredKeysHandler: Handler = async () => {
  const now = new Date().toISOString();
 
  const result = await dynamodb.scan({
    TableName: process.env.AUDIT_TABLE_NAME!,
    FilterExpression: 'begins_with(pk, :prefix) AND scheduledFor < :now AND #status = :pending',
    ExpressionAttributeNames: {
      '#status': 'status'
    },
    ExpressionAttributeValues: {
      ':prefix': 'DELETION#',
      ':now': now,
      ':pending': 'PENDING'
    }
  }).promise();
 
  for (const item of result.Items || []) {
    try {
      await iam.deleteAccessKey({
        UserName: item.userName,
        AccessKeyId: item.keyId
      }).promise();
 
      // Update status
      await dynamodb.update({
        TableName: process.env.AUDIT_TABLE_NAME!,
        Key: { pk: item.pk, sk: item.sk },
        UpdateExpression: 'SET #status = :completed, deletedAt = :now',
        ExpressionAttributeNames: { '#status': 'status' },
        ExpressionAttributeValues: { ':completed': 'COMPLETED', ':now': now }
      }).promise();
 
      console.log(`Deleted old key: ${item.keyId} for user: ${item.userName}`);
    } catch (error) {
      console.error(`Failed to delete key ${item.keyId}:`, error);
    }
  }
};

Monitoring and Alerting

CloudWatch Dashboard:

resource "aws_cloudwatch_dashboard" "key_rotation" {
  dashboard_name = "iam-key-rotation"
 
  dashboard_body = jsonencode({
    widgets = [
      {
        type = "metric"
        properties = {
          metrics = [
            ["AWS/Lambda", "Invocations", { stat = "Sum", label = "Total Runs" }],
            [".", "Errors", { stat = "Sum", label = "Errors" }],
            [".", "Duration", { stat = "Average", label = "Avg Duration" }]
          ]
          period = 86400
          stat   = "Sum"
          region = var.aws_region
          title  = "Lambda Execution Metrics"
        }
      },
      {
        type = "log"
        properties = {
          query = <<-EOQ
            SOURCE '/aws/lambda/iam-key-rotation'
            | fields @timestamp, @message
            | filter @message like /rotated key/
            | sort @timestamp desc
            | limit 20
          EOQ
          region = var.aws_region
          title  = "Recent Key Rotations"
        }
      }
    ]
  })
}
 
# Alert on rotation failures
resource "aws_cloudwatch_metric_alarm" "rotation_failures" {
  alarm_name          = "iam-key-rotation-failures"
  comparison_operator = "GreaterThanThreshold"
  evaluation_periods  = 1
  metric_name         = "Errors"
  namespace           = "AWS/Lambda"
  period              = 86400
  statistic           = "Sum"
  threshold           = 0
  alarm_description   = "Alert when key rotation fails"
  alarm_actions       = [aws_sns_topic.security_alerts.arn]
 
  dimensions = {
    FunctionName = aws_lambda_function.key_rotation.function_name
  }
}

Compliance Reporting

Generate quarterly reports for auditors:

export const generateComplianceReportHandler: Handler = async () => {
  // Query all rotations in the last 90 days
  const ninetyDaysAgo = new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString();
 
  const rotations = await dynamodb.query({
    TableName: process.env.AUDIT_TABLE_NAME!,
    IndexName: 'timestamp-index',
    KeyConditionExpression: '#timestamp > :start',
    ExpressionAttributeNames: {
      '#timestamp': 'timestamp'
    },
    ExpressionAttributeValues: {
      ':start': ninetyDaysAgo
    }
  }).promise();
 
  // Get all current users and check key ages
  const users = await iam.listUsers().promise();
  const complianceStatus = [];
 
  for (const user of users.Users || []) {
    const accessKeys = await iam.listAccessKeys({
      UserName: user.UserName
    }).promise();
 
    for (const key of accessKeys.AccessKeyMetadata || []) {
      if (key.Status !== 'Active') continue;
 
      const ageInDays = calculateAgeInDays(key.CreateDate!);
 
      complianceStatus.push({
        userName: user.UserName,
        keyId: key.accessKeyId,
        ageInDays: ageInDays,
        compliant: ageInDays < 90
      });
    }
  }
 
  const totalKeys = complianceStatus.length;
  const compliantKeys = complianceStatus.filter(k => k.compliant).length;
  const complianceRate = (compliantKeys / totalKeys) * 100;
 
  const report = {
    reportDate: new Date().toISOString(),
    period: '90 days',
    totalKeys: totalKeys,
    compliantKeys: compliantKeys,
    nonCompliantKeys: totalKeys - compliantKeys,
    complianceRate: complianceRate.toFixed(2) + '%',
    totalRotations: rotations.Items?.length || 0,
    details: complianceStatus
  };
 
  // Store report in S3
  const s3 = new (require('aws-sdk').S3)();
  await s3.putObject({
    Bucket: process.env.REPORTS_BUCKET!,
    Key: `compliance-reports/iam-keys-${new Date().toISOString().split('T')[0]}.json`,
    Body: JSON.stringify(report, null, 2),
    ContentType: 'application/json'
  }).promise();
 
  // Send summary to security team
  await sendComplianceReportEmail(report);
 
  return report;
};

Best Practices

1. Tag Users with Email Addresses

# Add email tags to IAM users for notifications
aws iam tag-user \
  --user-name john.doe \
  --tags Key=Email,Value=john.doe@company.com

2. Exemptions for Service Accounts

const EXEMPTED_USERS = [
  'terraform-ci',
  'github-actions',
  'datadog-agent'
];
 
// In your Lambda function
if (EXEMPTED_USERS.includes(key.userName)) {
  console.log(`Skipping exempted user: ${key.userName}`);
  continue;
}

3. Testing Before Production

# Test Lambda function locally
sam local invoke KeyRotationFunction \
  --event test-event.json
 
# Test with one user first
export TEST_MODE=true
export TEST_USER=john.doe

4. Gradual Rollout

Week 1: Warning emails only (no rotation)
Week 2: Rotation for 5 test users
Week 3: Rotation for 25% of users
Week 4: Full rollout

Conclusion: Automation is Security

Manual key rotation doesn't scale and creates compliance gaps. Automated rotation:

  • Reduces breach risk: 90-day keys limit exposure window
  • Ensures compliance: SOC 2, PCI-DSS, ISO 27001 requirements met automatically
  • Saves time: 15 minutes/month manual work → 0 minutes
  • Provides audit trails: Complete DynamoDB history for auditors
  • Forces best practices: Users must retrieve keys from Secrets Manager

Action Items

  1. Audit current key ages: Run AWS CLI command to find keys over 90 days
  2. Deploy Lambda function: Use Terraform configuration provided
  3. Tag IAM users: Add email tags for notifications
  4. Test with yourself: Trigger rotation for your own test user
  5. Enable EventBridge schedule: Start daily scans
  6. Monitor for 30 days: Ensure no false positives or rotation failures

If you need help implementing automated IAM key rotation, schedule a consultation. We'll audit your current IAM setup, deploy the rotation system with custom exemptions and notification templates, and provide runbooks for your security team.

Need Help with Your Cloud Infrastructure?

Our experts are here to guide you through your cloud journey

Schedule a Free Consultation