Back to Checks
IAM user does not have active access keys older than 90 days
iam_rotate_access_key_90_days
Service: iamSeverity: medium
OfficialBuilt-inVerified
Categories:secrets
Resource Type:AwsIamUser
IAM user access keys are assessed via the credential report. For each active key, the last_rotated timestamp is compared to 90 days; keys exceeding this age are identified. Users without keys or with only recent rotations are noted.
Risk
Long-lived access keys widen the attack window. If a key is leaked in code, logs, or tooling, lack of rotation keeps it valid for abuse, enabling unauthorized API calls, data exfiltration, and tampering. This degrades confidentiality and integrity and can impact availability and cost through destructive or excessive operations.
Run this check with Prowler
Run in Prowler CloudRun
prowler aws --checks iam_rotate_access_key_90_daysRecommendation
Apply least privilege and limit static credentials:
- Rotate active access keys at or before
90 days - Prefer IAM roles with short-lived tokens
- Maintain only one active key during rotation; delete the old one
- Monitor
last_usedand remove dormant keys - Automate alerts and periodic reviews of key age
Remediation
CLI
aws iam update-access-key --user-name <USER_NAME> --access-key-id <ACCESS_KEY_ID> --status InactiveTerraform
resource "aws_iam_access_key" "<example_resource_name>" {
user = "<example_resource_name>"
status = "Inactive" # Critical: disables the access key to ensure no active key is older than 90 days
}Other
1Open the IAM console and go to Users
2Select the affected user
3Open the Security credentials tab
4Under Access keys, find any key older than 90 days
5Click Actions > Deactivate (or Delete) for that key
6Repeat for any other active keys older than 90 days
Check Code
import datetime
import pytz
from dateutil import parser
from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.iam.iam_client import iam_client
maximum_expiration_days = 90
class iam_rotate_access_key_90_days(Check):
def execute(self) -> Check_Report_AWS:
findings = []
for user in iam_client.credential_report:
# Search user in iam_client.users to get tags
user_tags = []
for iam_user in iam_client.users:
if iam_user.arn == user["arn"]:
user_tags = iam_user.tags
break
if (
user["access_key_1_last_rotated"] == "N/A"
and user["access_key_2_last_rotated"] == "N/A"
):
report = Check_Report_AWS(metadata=self.metadata(), resource=user)
report.region = iam_client.region
report.resource_id = user["user"]
report.resource_arn = user["arn"]
report.resource_tags = user_tags
report.status = "PASS"
report.status_extended = (
f"User {user['user']} does not have access keys."
)
findings.append(report)
else:
old_access_keys = False
if (
user["access_key_1_last_rotated"] != "N/A"
and user["access_key_1_active"] == "true"
):
access_key_1_last_rotated = datetime.datetime.now(
pytz.utc
) - parser.parse(user["access_key_1_last_rotated"])
if access_key_1_last_rotated.days > maximum_expiration_days:
old_access_keys = True
report = Check_Report_AWS(
metadata=self.metadata(), resource=user
)
report.region = iam_client.region
report.resource_id = f"{user['user']}-access-key-1"
report.resource_arn = user["arn"]
report.resource_tags = user_tags
report.status = "FAIL"
report.status_extended = f"User {user['user']} has not rotated access key 1 in over 90 days ({access_key_1_last_rotated.days} days)."
findings.append(report)
if (
user["access_key_2_last_rotated"] != "N/A"
and user["access_key_2_active"] == "true"
):
access_key_2_last_rotated = datetime.datetime.now(
pytz.utc
) - parser.parse(user["access_key_2_last_rotated"])
if access_key_2_last_rotated.days > maximum_expiration_days:
old_access_keys = True
report = Check_Report_AWS(
metadata=self.metadata(), resource=user
)
report.region = iam_client.region
report.resource_id = f"{user['user']}-access-key-2"
report.resource_arn = user["arn"]
report.resource_tags = user_tags
report.status = "FAIL"
report.status_extended = f"User {user['user']} has not rotated access key 2 in over 90 days ({access_key_2_last_rotated.days} days)."
findings.append(report)
if not old_access_keys:
report = Check_Report_AWS(metadata=self.metadata(), resource=user)
report.region = iam_client.region
report.resource_id = user["user"]
report.resource_arn = user["arn"]
report.resource_tags = user_tags
report.status = "PASS"
report.status_extended = f"User {user['user']} does not have access keys older than 90 days."
findings.append(report)
return findings