Cross-account access, least privilege and CI deployments all rely on assuming IAM roles instead of sharing keys.
An IAM role is an identity without long-term keys. Principals allowed by the role's trust policy can call STS assume_role and receive temporary credentials (access key, secret key, session token) valid for 15 minutes to the role's maximum session duration (1 hour by default, up to 12).
In code:
sts.assume_role(RoleArn=..., RoleSessionName="report-job", DurationSeconds=3600)returns aCredentialsdict withAccessKeyId,SecretAccessKey,SessionTokenandExpiration.- Build a
boto3.Session(aws_access_key_id=..., aws_secret_access_key=..., aws_session_token=...)from them and create clients. - Optional:
ExternalId(required by many third-party trust policies) and an inline sessionPolicyto restrict permissions further.
Credentials made this way don't refresh: after expiry, calls fail with ExpiredToken. For long-running processes, prefer letting botocore manage it: a profile in ~/.aws/config with role_arn and source_profile (or credential_source = Ec2InstanceMetadata / EcsContainer) makes boto3 assume the role and refresh credentials automatically before they expire.
Use a meaningful RoleSessionName (job name, user): it appears in CloudTrail, making audits possible. Verify with get_caller_identity(), whose ARN changes to assumed-role/<RoleName>/<session name>.
CI systems use the related assume_role_with_web_identity (OIDC tokens from GitHub Actions or GitLab) so no AWS keys are stored at all.
Example
import boto3
def session_for_role(role_arn, name, duration=3600):
creds = boto3.client("sts").assume_role(
RoleArn=role_arn, RoleSessionName=name, DurationSeconds=duration)["Credentials"]
return boto3.Session(
aws_access_key_id=creds["AccessKeyId"],
aws_secret_access_key=creds["SecretAccessKey"],
aws_session_token=creds["SessionToken"],
region_name="us-east-1"), creds["Expiration"]
audit, expires = session_for_role("arn:aws:iam::123456789012:role/AuditReadOnly", "weekly-audit")
print(audit.client("sts").get_caller_identity()["Arn"])
# arn:aws:sts::123456789012:assumed-role/AuditReadOnly/weekly-audit
print(expires.tzinfo is not None) # True: an aware datetime; no auto-refresh!
# Auto-refreshing alternative in ~/.aws/config:
# [profile audit]
# role_arn = arn:aws:iam::123456789012:role/AuditReadOnly
# source_profile = default
# -> boto3.Session(profile_name="audit") refreshes credentials automaticallyRemember
- assume_role returns temporary credentials for a role you're trusted to use
- Sessions built from raw assume_role credentials don't refresh
- Profiles with role_arn + source_profile refresh automatically
- Use descriptive RoleSessionNames for CloudTrail audits
Watch out for
- Caching assumed-role credentials in a long-running worker and failing with ExpiredToken after an hour.