Your AWS IAM password policy lets console passwords live forever, which means a leaked or shared credential stays valid until someone notices. Set a maximum password age (commonly 90 days) with aws iam update-account-password-policy --max-password-age 90, then plan your move to SSO and hardware MFA.
This check flags AWS accounts where the IAM password policy has no maximum age configured for console (web) passwords. Without an expiration, a password set once during onboarding can remain valid for the entire life of the account. That sounds convenient until you trace what happens when that password ends up in a Slack message, a git commit, a phishing capture, or an ex-employee's password manager.
The check is account-level, not per-user. It inspects the single IAM account password policy and looks for the presence and value of the MaxPasswordAge field. If it is missing, the policy never forces a rotation.
What this check detects
AWS lets you define one password policy per account that governs all IAM users who log in to the console. That policy controls minimum length, character requirements, reuse prevention, and password expiration. The account_passwordexpiry check looks specifically at whether expiration is enabled.
You can see the current policy with a single command:
aws iam get-account-password-policy
A policy with no expiration returns something like this, with no MaxPasswordAge key at all:
{
"PasswordPolicy": {
"MinimumPasswordLength": 8,
"RequireSymbols": false,
"RequireNumbers": true,
"RequireUppercaseCharacters": false,
"RequireLowercaseCharacters": true,
"ExpirePasswords": false
}
}
Notice ExpirePasswords is false. That field is read-only and flips to true automatically once you set a max age. The check is essentially confirming that this stays false.
Note: If you have never created a password policy at all, get-account-password-policy returns a NoSuchEntity error. That is also a finding, since the account falls back to AWS defaults, which include no expiration and a weak 8-character minimum.
Why it matters
Password expiration is a debated control, and we will get to the nuance. But in the context of long-lived IAM console passwords with no other safeguards, the risk is concrete.
Credentials outlive their usefulness
Engineers leave. Contractors finish their work. A service account gets created for a one-off task and forgotten. If those passwords never expire, the credentials stay live indefinitely. Offboarding processes are supposed to disable these accounts, but in practice they miss things, especially in accounts with dozens of IAM users created over several years.
Leaked passwords stay valid
Passwords leak in ways you do not control: a screenshot in a support ticket, reused credentials exposed in an unrelated breach, a phishing page that captures the login. Without rotation, the window of exposure is unbounded. An attacker who grabbed a password 14 months ago can still use it today.
Compliance frameworks expect it
CIS AWS Foundations Benchmark, PCI DSS, and several SOC 2 control mappings reference password expiration. If you are pursuing or maintaining any of these, an account with no expiration policy is a finding an auditor will write up.
Warning: Modern guidance from NIST (SP 800-63B) actually recommends against forced periodic rotation when strong MFA and breach monitoring are in place, because it pushes users toward weak, incremental passwords like Summer2024! then Summer2025!. Expiration is a compensating control for accounts that still rely on passwords alone. The real fix is reducing your reliance on IAM console passwords altogether. More on that below.
How to fix it
The direct remediation is to set a maximum password age. Ninety days is a common baseline and satisfies most compliance benchmarks.
aws iam update-account-password-policy \
--max-password-age 90 \
--minimum-password-length 14 \
--require-uppercase-characters \
--require-lowercase-characters \
--require-numbers \
--require-symbols \
--password-reuse-prevention 24 \
--allow-users-to-change-password
A few notes on the flags above, since update-account-password-policy replaces the entire policy rather than patching individual fields:
- --max-password-age 90 turns on expiration. Accepted range is 1 to 1095 days.
- --minimum-password-length 14 exceeds the CIS minimum of 14 and the weak AWS default of 8.
- --password-reuse-prevention 24 stops users from cycling back to a recent password.
- --allow-users-to-change-password lets users rotate their own passwords without an admin, which you need once expiration is on.
Warning: Because this command overwrites the existing policy, run aws iam get-account-password-policy first and carry over every setting you want to keep. Omitting a flag resets that property to its default, not to its previous value.
Console steps
- Open the IAM console and go to Account settings in the left navigation.
- Under Password policy, choose Edit.
- Check Turn on password expiration and set the period to 90 days.
- Set Password expiration requires administrator reset based on your preference. Leaving it off lets users reset their own expired passwords.
- Confirm the other settings (length, reuse, character requirements) match your standard, then save.
Terraform
If you manage IAM with Terraform, the aws_iam_account_password_policy resource is the source of truth:
resource "aws_iam_account_password_policy" "strict" {
minimum_password_length = 14
require_lowercase_characters = true
require_uppercase_characters = true
require_numbers = true
require_symbols = true
max_password_age = 90
password_reuse_prevention = 24
allow_users_to_change_password = true
}
Apply it and the drift is gone. Anyone who later turns off expiration in the console will get reverted on the next terraform apply, which is exactly what you want.
Tip: Define this resource once in a baseline module and pull it into every account through your landing zone or org bootstrap. A password policy is identical across accounts, so there is no reason to copy-paste it.
How to prevent it from recurring
Fixing one account is easy. Keeping every account compliant as your org grows is the actual challenge. A few layers help.
Policy as code in CI
If your password policy is in Terraform, add a check that fails the pipeline when someone removes or weakens it. With OPA or Conftest you can assert on the planned state:
# Generate a plan and convert to JSON for policy evaluation
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json
# Evaluate against your Rego rules
conftest test tfplan.json --policy ./policies
A minimal Rego rule that rejects a missing or excessive max age:
package main
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_iam_account_password_policy"
age := resource.change.after.max_password_age
age == null
msg := "IAM password policy must set max_password_age"
}
deny[msg] {
resource := input.resource_changes[_]
resource.type == "aws_iam_account_password_policy"
age := resource.change.after.max_password_age
age > 90
msg := sprintf("max_password_age %d exceeds the 90-day limit", [age])
}
Service Control Policies
You cannot directly enforce a password policy value through an SCP, but you can deny the actions that would weaken it from any non-approved principal. In an AWS Organization, restrict who is allowed to call iam:UpdateAccountPasswordPolicy and iam:DeleteAccountPasswordPolicy so member account admins cannot quietly remove expiration.
Continuous detection
Schedule a recurring scan so a drift is caught within hours, not at the next audit. Lensix runs the account_passwordexpiry check on a schedule across every connected AWS account and alerts when the policy is missing or non-compliant, which closes the gap between an accidental change and someone noticing.
Tip: Pair the password policy check with an AWS Config managed rule (iam-password-policy) for native, in-account remediation. Config can auto-remediate via an SSM document when it detects drift, and Lensix gives you the cross-account view on top.
Best practices
Setting expiration closes the immediate finding, but the durable answer is to stop depending on IAM console passwords for human access.
- Move humans to IAM Identity Center (SSO). Federated access through your identity provider means short-lived sessions and no standing console passwords to expire in the first place. This removes the root cause rather than patching it.
- Require MFA on every account that retains a password. Expiration matters far less when a stolen password alone cannot log in. Enforce MFA with an IAM policy that denies actions when
aws:MultiFactorAuthPresentis false. - Eliminate human use of the root user. The root account has its own password and is exempt from the IAM password policy. Lock it behind hardware MFA and stop using it for daily work.
- Audit IAM users regularly. Use the IAM credential report (
aws iam generate-credential-report) to find passwords that have not been used in months and accounts that never logged in. These are prime candidates for removal. - Standardize the policy across the org. A 90-day expiration in one account and no policy in three others is the situation that produces incidents. Make the baseline identical everywhere through code.
Note: Expiration only applies to IAM users with console passwords. It has no effect on access keys, which are a separate credential type with their own rotation problem. If your engineers authenticate with access keys, address that with a key age check rather than the password policy.
Treat this check as a signal, not just a box to tick. An account with no password expiration usually has other identity gaps nearby: stale users, missing MFA, and root credentials in active use. Fix the policy today, then use the finding as a prompt to move toward federated, short-lived access where forced rotation becomes irrelevant.

