Security

How to Replace AWS Access Keys with IAM Roles

Long-lived AWS access keys in app config are a standing risk. Here is how to replace AWS access keys with IAM roles for compute and OIDC for CI, with a step-by-step migration plan.

6 min readSecurity

Many AWS setups still carry an access key and secret in an environment file, a CI variable, or a config map. It works, so nobody touches it. But a static key is one of the easiest credentials to lose and one of the hardest to track. This guide shows how to replace AWS access keys with IAM roles, what to use for each type of workload, and how to migrate without an outage.

Why long-lived keys in app config are a problem

An access key for an IAM user does not expire on its own. It stays valid until someone deactivates or deletes it. That has a few practical effects:

  • The key gets copied. It lands in .env files, deploy scripts, CI settings, laptops, and chat messages. Each copy is another place it can leak from.
  • It is hard to tie to one workload. When several services share a key, you cannot tell which one made a call, and you cannot revoke it without breaking all of them.
  • Rotation is manual, so it gets skipped. Someone has to create the new key, update every consumer, and remove the old one.

AWS states this directly in its IAM best practices: require workloads to use temporary credentials with IAM roles to access AWS, and rely on long-term access keys only for use cases that truly need them.

What to use instead

The idea is the same everywhere. Instead of storing a secret in the app, the platform hands the app short-lived credentials for an IAM role, and the AWS SDK picks them up on its own. The official docs say that when you build on a compute service such as Amazon EC2 or Lambda, AWS delivers the temporary credentials of an IAM role to that compute resource, and applications using an AWS SDK discover and use them.

EC2: instance profiles

For an application on EC2, you attach an IAM role through an instance profile. If you use the IAM console, the instance profile is managed for you. If you use the CLI or API, you create the instance profile and add the role to it as separate steps. The app then gets temporary credentials from the instance metadata service, and they refresh automatically before they expire. One role per instance, shared by every app on it.

ECS: task roles

On Amazon ECS, give each task its own IAM role. The permissions in that role go to the containers in the task. Do not confuse it with the task execution role, which the ECS agent uses to pull images and run the task. Your application code needs the task role. Task credentials also carry the task in the session context, so CloudTrail logs show which task the credentials were issued for.

EKS: Pod Identity

On Amazon EKS, EKS Pod Identity lets you associate an IAM role with a Kubernetes service account. Pods that use that service account get credentials for the role, and the SDK is configured through environment variables that EKS sets.

CI deploys: OIDC

Pipelines run outside AWS, so the platform cannot hand them a role directly. The usual fix is OpenID Connect. Your CI provider issues a signed token for each job, and AWS exchanges it for temporary credentials through the STS AssumeRoleWithWebIdentity API. AWS lists this as a supported way to deliver temporary credentials to workloads outside AWS.

For GitHub Actions, the setup looks like this:

  1. In IAM, add an OpenID Connect identity provider with the URL https://token.actions.githubusercontent.com and the audience sts.amazonaws.com.
  2. Create a role whose trust policy allows web identity from that provider.
  3. Restrict the trust policy on the sub claim, so that only your repository, and ideally only a specific branch or environment, can assume the role. GitHub's documentation shows examples such as a single branch or every branch of one repository.
  4. In the workflow, grant the id-token: write permission and use the aws-actions/configure-aws-credentials action with the role-to-assume setting.

No secret is stored in GitHub. The credentials exist only for the job.

Outside AWS

For servers on-premises or in another cloud, AWS lists IAM Roles Anywhere (which uses X.509 certificates from your own PKI) and the SAML and web identity STS calls as ways to avoid static keys. Where a tool truly cannot use roles, keep that key tightly scoped and rotate it.

Step-by-step migration plan

Do this one workload at a time. The order matters, because the old key should be the last thing you touch.

  1. Inventory. List every IAM user with access keys. The IAM console can show an Access key age column, and you can also pull a credential report. For each key, find out what uses it. The last used information for a key (the GetAccessKeyLastUsed API) shows the service and Region it last called, which helps you trace it. Also search your repositories, CI settings, secrets managers, and config stores for key names such as AWS_ACCESS_KEY_ID.
  2. Create a role per workload. Write a trust policy for the right principal (the EC2 service, ecs-tasks.amazonaws.com, the Pod Identity principal, or your OIDC provider). Attach a policy scoped to what that workload actually does. IAM Access Analyzer can generate a policy from CloudTrail activity, which is a useful starting point after you review it.
  3. Attach the role to the compute. Add the instance profile, set the task role in the task definition, or create the Pod Identity association. For EC2, note that whoever launches an instance with a role needs the iam:PassRole permission for it. Scope that permission to the specific role.
  4. Remove the key from the app. Delete the environment variables, config entries, and secrets. The SDK looks through its credential provider chain, and static keys are one of the sources it checks. If you leave them in place, the app may keep using them and never touch the role. Removing them proves the role works.
  5. Deploy and test. Run the normal paths of the app, including background jobs and anything that runs rarely. Confirm calls succeed under the role, not the old user.
  6. Deactivate, do not delete. AWS advises against deleting the old key straight away, even if last used shows no activity. Set it to Inactive first, then watch for failures. Anything still using the key will fail at that point, and you can switch the key back to Active if you find something you missed.
  7. Delete after a waiting period. Once you are sure nothing depends on the key, delete it. If the IAM user existed only for that workload, remove the user too.
  8. Monitor. Use CloudTrail to check that calls now come from the role session, and look for any remaining use of old keys. Review access key age on a schedule so new static keys do not creep back.

Common mistakes

  • Leaving the old key in the environment. The SDK may still pick it up before the role, so the migration looks done when it is not.
  • Giving the role the same broad permissions the shared key had. A role is a chance to narrow access. Start from what the workload calls and trim from there.
  • Using a wide trust policy for OIDC. If the sub condition is missing or too loose, other repositories or branches may be able to assume your deploy role. Match the exact repository and, where you can, the branch or environment.
  • Confusing ECS roles. Putting permissions on the task execution role instead of the task role is a frequent cause of access denied errors in the app.
  • Ignoring the instance metadata service settings. AWS documents IMDSv2 as a session-oriented method and lets you configure an instance to accept only IMDSv2. Use a current SDK version, since older versions may not support it.
  • Deleting keys before checking last used. You find the forgotten scheduled job when it breaks. Deactivate first.

If you want a second pair of eyes on a migration, or someone to run it with your team, see our DevOps and cloud services or book a call.

Frequently Asked Questions

Free · no obligation

Need this built?

Book a free 30-minute call. We'll discuss your goals, give you honest advice, and a clear estimate — no obligation.

Related services

Ways we can help

Keep reading

Related articles