Skip to main content
INS // Insights

Eliminate AWS IAM User Access Keys

Updated August 2026 · 5 min read

Eliminate AWS IAM user access keys is the instruction. Rotation is the trap. A 90-day rotate still leaves a long-lived secret in CI, a laptop, or a vendor integration. Auditors using the IAM credential report still see access_key_1_active=true. Attackers using a leaked key still get the full policy until someone notices.

We run this as a credential-elimination program, not a Secrets Manager rotation project. The replacement is a role plus a federation or instance identity. The old key is disabled, soaked, then deleted.

Inventory That Is Not the IAM Console Click-Through

GetCredentialReport is the start, not the finish. For every active access key we add:

  • CloudTrail AuthClass / userIdentity last-used (report field + independent trail query)
  • Calling IP / user agent clusters (CI vs human vs vendor)
  • Attached policies vs observed actions (they diverge)
  • Secret locations we can find: GitHub Actions secrets, SSM, Lambda env, Terraform state comments (yes, really)

Keys with no CloudTrail in 90 days are disable candidates after a break-glass check. Keys used from one NAT Gateway CIDR are CI. Keys used from home ISPs are humans who should be on Identity Center instead.

This inventory is the AWS-user slice of non-human identity inventory. Humans with access keys are a process failure; workloads with access keys are an architecture failure.

Cutover Patterns That Work

Caller Replacement
GitHub Actions OIDC → IAM role (org-scale federation)
GitLab / other CI OIDC or platform identity, same trust-policy discipline
EC2 / ECS Instance profile / task role
Lambda Execution role
Foreign cloud job Cross-cloud federation
Third-party SaaS that demands a key Vendor-scoped role + external ID, or a dedicated key in a vault with SCP deny on iam:CreateAccessKey for humans — last resort, time-boxed
# deny creating IAM user keys in member accounts (break-glass excepted)
statement {
  sid    = "DenyCreateAccessKey"
  effect = "Deny"
  actions = ["iam:CreateAccessKey"]
  resources = ["*"]
  condition {
    test     = "StringNotLike"
    variable = "aws:PrincipalArn"
    values   = [var.break_glass_role_arn]
  }
}

If you only delete keys and leave CreateAccessKey open, they come back the next incident.

Disable, Soak, Delete

  1. Create the role and dual-run.
  2. Disable the key (UpdateAccessKey Inactive). Do not delete.
  3. Watch CloudTrail for InvalidClientTokenId / failed auth from the old key ID — that is leftover callers.
  4. After the soak, delete the key, then the user if it has no MFA-console purpose.
  5. Record the timestamps. That pack is CC6 evidence, not a Jira comment.

Rollback is re-enabling the key. That is the entire reason disable comes first. Teams that delete on Friday invent a worse key on Saturday.

AWS's own guidance on rotating access keys is operationally correct for the brief period you still have a key. It is not a destination. Destination is zero user keys for workloads.

Talk to us about your credential elimination pilot → rutagon.com/contact · 907-841-8407 · contact@rutagon.com.

Credential Report Cadence

Weekly GetCredentialReport into S3. Alert if access_key_1_active is true for any user not on a documented exception list. Exception list is named vendor integrations with expiry. Empty list is the goal.

Console-only users should have no keys. If they have keys, they are CLI users in disguise. Move them to Identity Center.

Terraform State

CI assumes a role to run Terraform. State is in S3 with a DynamoDB lock, encrypted, no IAM user. A leftover aws_iam_access_key resource in Terraform is a finding we grep for in the diagnostic. Delete the resource after cutover; do not leave it commented.

Eliminate AWS IAM user access keys with a disable-soak-delete

Eliminate AWS IAM user access keys by inventorying GetCredentialReport weekly into S3 and alarming on access_key_1_active for anyone not on a named, expiring exception list. AWS’s access key rotation guidance is for the brief period a key still exists. Destination is zero user keys for workloads.

Cutover: GitHub Actions OIDC, ECS task roles, Lambda roles, EC2 instance profiles, Identity Center for humans. Terraform aws_iam_access_key resources are grepped in the diagnostic; commented resources still scare us until deleted.

Disable, soak (CloudTrail shows no use), delete. Deleting first is how you outage a forgotten integration and then recreate keys “temporarily” for a year. Console-only humans with keys are CLI users in disguise — move them to Identity Center.

CI assumes a role. State is S3 + DynamoDB lock, encrypted. No IAM user in GitHub secrets named AWS_SECRET_ACCESS_KEY.

The vendor who “requires” an IAM user access key

Some SaaS still wants a key. We put them on a named exception with an expiry, a permissions boundary, CloudTrail last-used, and a calendar reminder to nag for OIDC. Infinite exceptions are how the credential report never goes green. If the vendor cannot move, the exception is still reviewed — it is not forgotten.

Humans with keys in ~/.aws/credentials on laptops are in scope. SSO is the replacement. We do not “rotate quarterly” as the destination.

Frequently Asked Questions

Isn't 90-day key rotation a SOC 2 control?

Rotation is a compensating control while a key exists. The cleaner control is eliminating the key. Auditors accept federation plus a credential report that shows no active user keys for workloads.

What about the root account access keys?

There should be none. If the credential report shows root keys, that is an incident, not a sprint story. Hardware MFA on root, no keys, break-glass runbook.

Can we keep one IAM user for Terraform state?

No. Use OIDC from CI or a role assumed from a locked-down runner. A dedicated "terraform" IAM user is still a long-lived key with iam:* in too many orgs we inventory.

How do we handle a vendor that will not federate?

Time-box a dedicated user in a dedicated account, IP-restrict if the vendor is stable, CloudTrail alarm on any other caller, quarterly expiry. Put the exception in the GRC tool. Do not pretend it is equivalent to federation.

Will deleting keys break CloudTrail history?

No. History remains. You lose the ability to use the key. That is the goal.