The credential provider chain in depth

boto3 · Expert · 12 min

When the wrong identity is used in production, or credentials expire mid-job, the fix requires knowing the chain precisely.

Credentials passed explicitly to client() or Session() always win. Otherwise botocore's resolver tries providers in order and uses the first that returns credentials:

  • Environment variables (AWS_ACCESS_KEY_ID, ...). Skipped when you pass profile_name= to a Session explicitly.
  • Profile-based providers for the active profile: assume role (role_arn), web identity (web_identity_token_file), IAM Identity Center/SSO (sso_session), the shared credentials file, console login, credential_process (an external command printing JSON), and static keys in the config file.
  • Legacy files (/etc/boto.cfg, ~/.boto).
  • Container credentials (AWS_CONTAINER_CREDENTIALS_RELATIVE_URI / _FULL_URI, used by ECS tasks and EKS Pod Identity).
  • Instance metadata (IMDS) on EC2.

session.get_credentials().method tells you which provider won: env, assume-role, sso, shared-credentials-file, container-role, iam-role.

Most providers return RefreshableCredentials: botocore checks expiry before each request and refreshes ahead of time (advisory and mandatory refresh windows), thread-safely. Assume-role and SSO results are cached on disk under ~/.aws/cli/cache and ~/.aws/sso/cache.

Classic surprises: stale AWS_ACCESS_KEY_ID in a shell overriding the instance role; a container without the metadata hop limit raised (IMDSv2 on Docker); a frozen credentials object (get_frozen_credentials()) used past expiry. Debug with boto3.set_stream_logger("botocore.credentials").

Example

import logging, os
import boto3

os.environ["AWS_ACCESS_KEY_ID"] = "AKIAEXAMPLE"          # simulate a stray env var
os.environ["AWS_SECRET_ACCESS_KEY"] = "secret"

creds = boto3.Session().get_credentials()
print(creds.method)                         # env: env vars beat profiles and roles

frozen = creds.get_frozen_credentials()     # a snapshot: never refreshes
print(frozen.access_key)                    # AKIAEXAMPLE

# See which providers are tried, in order, and which one wins:
boto3.set_stream_logger("botocore.credentials", logging.DEBUG)
boto3.Session().get_credentials()
# ... Looking for credentials via: env
# ... Found credentials in environment variables.

# credential_process example (~/.aws/config):
# [profile vault]
# credential_process = /usr/local/bin/vault-aws-creds --role deploy

Remember

  • Explicit credentials > env vars > profile providers > container > instance metadata
  • profile_name= on a Session skips env var credentials
  • creds.method reveals which provider supplied credentials
  • Refreshable credentials renew themselves; frozen snapshots don't

Watch out for

  • A leftover AWS_ACCESS_KEY_ID in the environment silently overriding the role you expected to use.

Check yourself

  1. On an EC2 instance with a role, AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are also set. Which identity does boto3 use?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: boto3.amazonaws.com/v1/documentation/api/latest/guide/credentials.html#configuring-credentials

Builds on

Unlocks

Nothing builds on this yet.