Back to blog
AWSBest PracticesCloud SecurityIdentity & AccessOperations & Compliance

IAM User Has Both a Password and Access Keys: Why It Matters and How to Fix It

Learn why AWS IAM users with both a console password and active access keys double your attack surface, plus step-by-step CLI fixes and prevention.

TL;DR

This check flags IAM users who have both a console login password and active programmatic access keys, which doubles their attack surface. Pick one access method per user, or better yet move humans to federated SSO and machines to roles, then remove the credential you do not need.

Most IAM users do not need to log into the AWS console and make API calls with long-lived access keys. When an account has both, you have given an attacker two independent ways in, and you have two sets of credentials to rotate, audit, and worry about. This Lensix check, user_passwordandkeys, looks for exactly that overlap.

It is a small misconfiguration on paper, but it is one of the most common findings in real AWS accounts, and it tends to cluster around the users you least want compromised: admins, break-glass accounts, and the developer who set up CI three years ago and never came back to clean up.


What this check detects

The check reviews every IAM user in the account and flags any user who has both of the following at the same time:

  • A console password (a login profile), meaning the user can sign in to the AWS Management Console.
  • At least one active access key (an access key ID and secret), used for the CLI, SDKs, and API calls.

Either one on its own is fine for the right use case. The flag fires when a single user carries both, because that almost always signals a credential that grew beyond its original purpose.

Note: A console password is for humans. Access keys are for code. When one identity holds both, it usually means a human user was handed programmatic keys for convenience, which is the pattern this check is designed to surface.


Why it matters

The core problem is attack surface. Every credential type is a separate path an attacker can exploit, and each path has a different theft profile.

Two doors, twice the risk

Console passwords get phished, reused across sites, or guessed. Access keys get committed to Git, baked into container images, left in shell history, or scraped from ~/.aws/credentials on a developer laptop. A user with both can be compromised through either route, and the two routes rarely share the same protections.

A common scenario: an admin enables MFA on their console login and feels secure. But their access keys, sitting in a dotfile on a workstation, have no MFA in front of them. An attacker who lifts those keys from a stolen laptop walks straight past the MFA you thought was protecting that identity.

Warning: MFA on console login does not protect access keys. Long-lived keys authenticate API calls directly and bypass the console entirely. This is the single most misunderstood part of IAM credential security.

Leaked keys are a top breach vector

Access keys committed to public repositories are scraped within minutes. The usual outcome is a cryptomining bill running into thousands of dollars, or worse, lateral movement if the key belongs to a privileged user. Because this check often catches admin users, the blast radius is large.

Audit and rotation overhead

Two credentials mean two rotation schedules, two things to revoke during an incident, and two entries to reason about when you ask "what can this identity do, and how was it used?" During an incident you want to disable an identity in one move, not chase down which credential the attacker actually used.


How to fix it

The fix depends on what the user is actually for. Start by figuring out whether the user is a human or a workload.

Step 1: Identify the affected users

List all users and check which have both a login profile and active keys. A quick way is to pull the credential report, which summarizes both in one CSV.

aws iam generate-credential-report

aws iam get-credential-report \
  --query 'Content' --output text | base64 -d > credential-report.csv

# Columns to inspect: password_enabled, access_key_1_active, access_key_2_active
column -t -s, credential-report.csv | less -S

For a single user, inspect the login profile and keys directly:

# Does the user have a console password?
aws iam get-login-profile --user-name alice

# Does the user have active access keys?
aws iam list-access-keys --user-name alice

Step 2: Decide which credential to keep

  • Human user who logs into the console: keep the password, remove the access keys. Humans should make API calls through short-lived credentials, not static keys.
  • Service or automation account: keep the access keys, remove the console password. A robot has no reason to log into the console.
  • Either: ideally migrate to a better pattern (see prevention below) and remove the IAM user credential entirely.

Step 3: Check usage before you delete anything

The credential report shows last-used dates for keys and the console. Confirm the credential you plan to remove is genuinely unused, or that you have a replacement ready.

aws iam get-access-key-last-used --access-key-id AKIAEXAMPLE12345

Danger: Removing an access key that is still in use will break any pipeline, script, or application relying on it. Confirm the key is unused, or roll out a replacement credential first, before running the delete commands below.

Step 4a: Remove the access keys (for a human user)

Deactivate first so you can roll back quickly if something breaks, then delete once you are confident nothing depended on it.

# Deactivate (reversible, good for a soak period)
aws iam update-access-key \
  --user-name alice \
  --access-key-id AKIAEXAMPLE12345 \
  --status Inactive

# Delete once confirmed safe
aws iam delete-access-key \
  --user-name alice \
  --access-key-id AKIAEXAMPLE12345

Step 4b: Remove the console password (for a service account)

aws iam delete-login-profile --user-name ci-deploy-bot

Tip: Deactivate before you delete. Setting a key to Inactive immediately stops it from working but keeps it recoverable for a week or two. If nobody complains, delete it for good. This turns a risky cleanup into a safe one.


How to prevent it from happening again

Cleaning up is reactive. The durable fix is to stop minting long-lived credentials in the first place.

Move humans to federated SSO

Use AWS IAM Identity Center (formerly AWS SSO) or an external IdP. Humans authenticate through their identity provider and assume roles for temporary credentials. There is no console password and no access key to leak, so this check stops firing for human users entirely.

Move workloads to roles

EC2, ECS, Lambda, and EKS workloads should use IAM roles, not access keys. For CI/CD pipelines running outside AWS (GitHub Actions, GitLab), use OIDC federation to assume a role with short-lived credentials.

{
  "Effect": "Allow",
  "Principal": {
    "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
  },
  "Action": "sts:AssumeRoleWithWebIdentity",
  "Condition": {
    "StringEquals": {
      "token.actions.githubusercontent.com:sub": "repo:my-org/my-repo:ref:refs/heads/main"
    }
  }
}

Gate it with policy-as-code

Catch the misconfiguration before it ships. If you manage IAM in Terraform, a custom Sentinel or OPA policy can reject any user resource that defines both an aws_iam_user_login_profile and an aws_iam_access_key.

Here is a Conftest/OPA rule against a Terraform plan in JSON:

package main

deny[msg] {
  users := {u | u := input.resource_changes[_]; u.type == "aws_iam_user"}
  login := {l.change.after.user | l := input.resource_changes[_]; l.type == "aws_iam_user_login_profile"}
  keys  := {k.change.after.user | k := input.resource_changes[_]; k.type == "aws_iam_access_key"}
  user := login[_]
  keys[user]
  msg := sprintf("IAM user '%s' has both a console password and an access key", [user])
}

Tip: Run continuous detection rather than relying on a one-time cleanup. Lensix re-runs user_passwordandkeys on a schedule, so when someone hands a service account a console password during a late-night debugging session, you find out the next day instead of during an audit.

Use an SCP to block long-lived keys

At the organization level, a Service Control Policy can deny iam:CreateAccessKey outright in accounts that have fully migrated to federated access, removing the temptation entirely.


Best practices

  • One credential type per identity. Humans get console or SSO access. Machines get roles or, as a last resort, keys. Never both on one user.
  • Prefer temporary credentials. Roles and STS tokens expire on their own, which removes the rotation burden and shrinks the window an attacker can use a stolen credential.
  • Enforce MFA on every console login, and remember it does nothing for access keys, so reduce your reliance on access keys instead.
  • Rotate any keys you genuinely need on a fixed schedule, and alert on keys older than 90 days.
  • Scan repos and images for leaked keys with tools like git-secrets or trufflehog in CI.
  • Review the credential report regularly to spot unused passwords and keys before an attacker finds them first.

The goal is not just to clean up the users this check flags today. It is to reach a state where no human holds a static access key and no machine holds a console password, so the finding has nowhere left to appear.