Back to Checks
aws

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 Cloud
Run
prowler aws --checks iam_rotate_access_key_90_days

Recommendation

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_used and 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 Inactive

Terraform

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