Cloud Security & IAM

Stop Using Long-Term Credentials: The IAM Key Problem Across AWS, GCP and Azure

You created an IAM user, generated an access key, and pasted it into a config file. Job done. Except that key doesn't expire, probably has more permissions than it needs, and might already be in a place it shouldn't be. This is how most cloud breaches start.

IAM AWS GCP Azure Security Zero Trust

What's in this article

  1. What exactly is a long-term credential
  2. AWS IAM users — the original sin
  3. GCP service account keys — a JSON file of unlimited power
  4. Azure — no IAM users, but still has the same traps
  5. When this goes wrong in the real world
  6. The fix: roles and short-lived credentials
  7. What to do right now
01

What exactly is a long-term credential

A long-term credential is any secret — a key, a password, a token — that does not expire on its own and stays valid indefinitely until someone manually revokes it. You create it once, copy it somewhere, and unless you actively go back and rotate or delete it, it just keeps working. Forever.

That is the problem in a single sentence. The credential has no expiry clock. Which means if it leaks — into a GitHub repo, a Slack message, an environment variable in a Docker image, a CI/CD log — whoever finds it has access to your cloud environment for as long as they want, until you notice and react.

The attacker's timeline vs yours. You find out a key leaked when something breaks or a security alert fires. By then, the attacker may have been inside your environment for days, weeks, or months. Long-term credentials are valuable precisely because the window of exploitation is entirely controlled by the attacker, not by you.

Compare that to a short-lived credential — a role-assumed session token, for example — which expires in 15 minutes to a few hours. If that leaks, the attacker has a very narrow window. In most cases, by the time they try to use it, it is already expired. That asymmetry is the entire argument for roles over keys.

02

AWS IAM users — the original sin

AWS IAM users are the most well-known example of this pattern. An IAM user is a permanent identity in your AWS account. You attach policies to it, and then you generate access keys — an Access Key ID and a Secret Access Key. That pair works forever, from anywhere, with no MFA required by default, as long as the policy attached to the user allows it.

Here is the lifecycle that plays out in most teams:

  1. Developer needs to run something against AWS from a CI/CD pipeline or their local machine.
  2. Someone creates an IAM user called deploy-user or jenkins-svc.
  3. Keys are generated and pasted into a GitHub Actions secret, a .env file, or a config on a server.
  4. The person who created the user leaves the team. Nobody thinks about the key again.
  5. Eight months later, the key is still active, possibly with a lot of permissions, sitting in a repo that was accidentally made public last quarter.
AWS's own recommendation: AWS has explicitly advised against creating long-term access keys for IAM users and has been pushing towards IAM Identity Center (SSO) for human access and IAM Roles for workloads for years. The guidance is clear. The adoption, less so.

The other problem with IAM users is that they are account-scoped identities. A key for an IAM user in your production account has no context about what service is using it, what environment it belongs to, or what it is supposed to be doing. A compromised key is just... a key. The blast radius is whatever the attached policy allows.

What long-term AWS credentials actually look like

This is what an exposed AWS credential looks like in the wild:

[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Keys starting with AKIA are long-term IAM user keys. Keys starting with ASIA are short-lived STS session tokens — those are the ones you want. If you grep your codebase for AKIA and find results, stop reading and go fix that first.

03

GCP service account keys — a JSON file of unlimited power

GCP handles human identity differently from AWS — there is no concept of a standalone GCP user that exists purely inside the project. Human users authenticate via Google accounts or Workspace identities. But for workloads and automation, GCP has service accounts. And service accounts have a feature that is the source of enormous pain: downloadable JSON keys.

When you create a service account key in the GCP Console, you get a JSON file that looks like this:

{
  "type": "service_account",
  "project_id": "my-prod-project",
  "private_key_id": "abc123...",
  "private_key": "-----BEGIN RSA PRIVATE KEY-----\n...",
  "client_email": "my-sa@my-prod-project.iam.gserviceaccount.com",
  "client_id": "123456789",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token"
}

That JSON file is a portable, complete credential. It does not expire. It works from any machine, anywhere in the world. It requires no network access to GCP to validate — the private key is inside the file itself. And the service account it belongs to often has project-level Editor or Owner permissions, because that was the easiest way to make the CI pipeline work at the time.

GCP's own documentation explicitly warns against service account keys. Google's best practice guide calls them a security risk and recommends Workload Identity Federation as the preferred alternative for essentially every use case where a key might be tempting. Despite this, they remain extremely common.

What makes service account keys particularly dangerous compared to AWS IAM keys:

04

Azure — no IAM users, but still has the same traps

Azure is worth discussing separately because it gets this part architecturally right in one specific way: there is no Azure equivalent of an AWS IAM user or a GCP service account key for human access. Humans authenticate via Entra ID (formerly Azure Active Directory). There is no "create a human user with an API key" option in the way AWS or GCP offer it. That is genuinely better.

But Azure absolutely still has long-term credential risks. They just show up in different places, and teams often do not think of them as "credentials" because they do not look like keys:

Storage account keys

Every Azure storage account comes with two 512-bit access keys that grant full control over the entire storage account — read, write, delete, everything. They do not expire. Many applications that integrate with Azure Blob Storage or Azure Files are built using these keys, pasted into connection strings, checked into app configuration, and promptly forgotten.

DefaultEndpointsProtocol=https;AccountName=mystorageaccount;
AccountKey=dGhpcyBpcyBub3QgYSByZWFsIGtleWJ1dCBpdCBsb29rcyBleGFjdGx5IGxpa2Ugb25l==;
EndpointSuffix=core.windows.net

App registration client secrets

When you create an App Registration in Entra ID to allow a service or application to authenticate, you generate a client secret. This secret can be configured to expire, but the default is often one or two years — and when it eventually expires, the rushed fix is usually to generate a new secret with an even longer expiry and paste it back into the configuration. The cycle repeats.

SAS tokens with no expiry

Shared Access Signatures (SAS) are meant to be short-lived, scoped tokens for delegated access to Azure Storage. In practice, teams generate account-level SAS tokens with expiry dates set years in the future, share them in URLs embedded in documentation or application configs, and then cannot revoke them without rotating the underlying storage account key — which breaks everything else that depends on it.

LearnCloud FinOps — discount code for FinOpsX event and FinOps certifications

Discount code for FinOpsX event and FinOps certifications — LEARNCLOUDX26 | LEARNCLOUD

05

When this goes wrong in the real world

This is not a theoretical risk. Long-term credentials have been at the centre of some of the most significant cloud security incidents in recent years.

Capital One (2019) — $80 million fine

A misconfigured WAF allowed an attacker to perform a Server-Side Request Forgery (SSRF) attack and query the EC2 instance metadata service. The metadata service returned the IAM role credentials attached to the instance — which in this case had excessive S3 permissions. The attacker used those credentials to exfiltrate over 100 million customer records. The IAM role itself was the right approach; the problem was the permissions were far too broad. The lesson: even short-lived credentials cause damage if over-permissioned. Watch a detailed technical video about it here.

Tesla (2018) — cryptojacking via exposed Kubernetes dashboard

Tesla's Kubernetes admin console was publicly accessible without authentication. An attacker found it, accessed the cluster, and discovered AWS credentials stored as environment variables in the pods. Those credentials were used to spin up EC2 instances for cryptocurrency mining. The credentials were AWS access keys — long-term, static, sitting in plaintext environment variables inside containers. The fix would have been IAM roles for the EC2 instances running the Kubernetes nodes.

Codecov (2021) — supply chain attack via CI credential exposure

Attackers compromised Codecov's Bash uploader script and modified it to exfiltrate environment variables from CI/CD pipelines that used it. Hundreds of companies had their CI secrets stolen — including AWS access keys, GCP service account credentials, and various API tokens. Because these were long-term credentials, attackers had persistent access to downstream victims' environments even after Codecov disclosed the breach. Short-lived credentials would have expired before most victims even knew about the incident.

The common thread across all three incidents is not sophisticated zero-day exploits. It is long-lived credentials sitting somewhere they should not be, with more access than they should have. The attacker's job is to find them. That part is often trivially easy.
06

The fix: roles and short-lived credentials

Every major cloud has a well-supported, production-ready answer to this problem. The underlying concept is the same: instead of a static key that represents a permanent identity, you assume a role at runtime. The cloud issues a short-lived token — valid for minutes to hours — scoped to exactly what that workload needs. When the token expires, it is gone. There is nothing to leak that remains useful for long.

🟠
AWS: IAM Roles

Attach an IAM Role to your EC2 instance, Lambda function, ECS task, or EKS pod. The workload calls the metadata service and gets short-lived STS credentials automatically. No keys required. For humans, use IAM Identity Center (SSO). For GitHub Actions, use OIDC federation — the pipeline assumes a role directly without any stored secret.

🔵
GCP: Workload Identity Federation

Instead of downloading a service account JSON key, configure Workload Identity Federation so your workload (a GitHub Actions runner, a Kubernetes pod, an external system) can impersonate a service account by exchanging a short-lived OIDC token. No file to download, no key to rotate, nothing to leak.

🟦
Azure: Managed Identities

Assign a System-assigned or User-assigned Managed Identity to your VM, App Service, Function App, or AKS workload. Azure handles credential issuance and rotation entirely. The application calls the local metadata endpoint and gets a token. There is no secret to manage, store, or rotate. For external workloads, use Federated Identity Credentials on App Registrations.

For human access, the answer across all three clouds is federated identity — your corporate identity provider (Google Workspace, Entra ID, Okta, or similar) issues a short-lived assertion that the cloud exchanges for a scoped session. AWS IAM Identity Center, GCP Workforce Identity Federation, and Azure Entra ID all support this. Humans should never have permanent cloud-native user accounts with static keys.

How short-lived the tokens actually are

For reference on what "short-lived" means in practice:

A credential that expires in an hour has a fundamentally different security profile than one that was created three years ago and never rotated. The former requires the attacker to use it immediately, in a narrow window, with full operational capability on their side. The latter just sits there, patiently waiting to be found.

07

What to do right now

If you are managing cloud environments today and you know long-term credentials exist somewhere in your setup, here is a pragmatic starting point — not a full remediation programme, just the things that matter most immediately.

Audit what you have

On AWS, pull the IAM Credential Report (available in the console or via aws iam generate-credential-report). It shows every IAM user, when their access keys were last used, and whether keys exist that have never been used. Any key not used in 90 days should be disabled. Any key not used in 180 days should be deleted.

On GCP, use the Security Command Center or run a query in Asset Inventory for service account keys older than 90 days. GCP now has an Organisation Policy constraint (iam.disableServiceAccountKeyCreation) that you can enforce to prevent new keys from being created entirely.

On Azure, review App Registration secrets and storage account keys in Entra ID and the Azure Portal. Microsoft Defender for Cloud will flag secrets nearing expiry and credentials with excessive permissions — if you are not using it, turn it on.

A quick win for GitHub repositories: Enable GitHub's secret scanning on all repositories. It automatically detects known formats for AWS access keys, GCP service account key fragments, Azure connection strings, and dozens of other credential patterns. It runs on every push and will notify you before an attacker finds what you missed.

Stop creating new ones

The fastest way to reduce your exposure is to stop the supply. In AWS, use Service Control Policies to deny iam:CreateAccessKey for IAM users in environments where roles are available. In mature enteprises, IAM user creation is treated as an exception, and it involves a long difficult approval process. In GCP, enforce the iam.disableServiceAccountKeyCreation org policy. In Azure, restrict who can create App Registration secrets via Entra ID roles.

Check out this detailed video about building a strong IAM model for your enterprise.

Migrate workloads to roles

Pick the highest-risk credentials first — the ones with the broadest permissions, used in CI/CD pipelines, or stored in places you are not fully confident about. Replace them with the role-based equivalent for their platform. For most common use cases — GitHub Actions, Lambda, EC2, App Service, GKE — the migration is well-documented and takes hours, not weeks.


The core principle to take away

Every credential is a liability. A long-term credential is an indefinite liability. The goal is not to manage credentials better — it is to eliminate the need for them entirely wherever the platform gives you a role-based alternative. In 2026, across AWS, GCP, and Azure, that alternative exists for virtually every workload pattern you will encounter. There is no longer a good technical reason to be generating static keys for machines or for people.

The question to ask whenever a key is being created: what role can replace this? If the answer is "I'm not sure," that is the thing worth figuring out. The key can wait.

I hope you found this useful, please share it!

✓ Link copied to clipboard
Mayank Pandey

About the Author

Mayank Pandey

AWS Community Hero and Cloud Architect with 15+ years of experience. AWS Solutions Architect Professional, FinOps Practitioner, and AWS Authorized Instructor. Creator of the KnowledgeIndia YouTube channel (80,000+ subscribers). Based in Melbourne, Australia.