Cloud 14 min read

AWS CLI First #2: Multi-Account Profiles, STS AssumeRole & MFA-Enforced Temporary Credentials

Graduate from static access keys to enterprise-grade cloud security. Learn how to configure multi-account profiles, assume IAM roles via AWS STS, enforce MFA on the terminal, and automate profile chaining in ~/.aws/config using a simulated zero-privilege engineer.

Mohammad Rizky Prawira portrait
Mohammad Rizky Prawira
terminal Series Guide Part 2 of 17

AWS with CLI: The Terminal-First Mastery Path

Part of the CLI-first comprehensive learning path from foundational setup to production cloud architectures.

In Part 1: Installation, Root Setup & IAM Admin User Provisioning, we established our terminal environment and generated long-lived access keys for our initial administrator user (cloud-dev-admin).

While static administrator access keys are convenient for initial bootstrapping, in real-world production environments giving developers permanent administrative keys is a critical security vulnerability:

  1. Permanent Blast Radius: If a developer’s laptop is compromised or a static key is accidentally committed to a repository, attackers retain unexpiring access until an administrator manually revokes it.
  2. No Automatic Expiration: Static keys never expire on their own.
  3. No Native MFA Gating: A stolen static access key can be used from any IP address worldwide without triggering a two-factor authentication challenge.

In this guide, we transition to modern cloud security: Dynamic Identity via AWS Security Token Service (STS) and IAM Roles.

By the end of this guide, you will:

  • Understand the architectural difference between static keys (AKIA...) and temporary session tokens (ASIA...).
  • Master the dual structure of an IAM Role: Trust Policy (Who can assume) vs Permission Policy (What it can do).
  • Demystify AWS resource addressing by decoding the Amazon Resource Name (ARN) syntax.
  • Simulate an enterprise workflow by provisioning a Zero-Privilege Engineer (engineer-bob) and an assumed deployment role (CloudStagingDeployer).
  • Provision Virtual MFA devices from the CLI and enforce aws:MultiFactorAuthPresent conditions.
  • Configure Automated Profile Chaining in ~/.aws/config for seamless, 1-hour cached terminal sessions with interactive MFA prompts.

💰 Estimated Lab Cost

ComponentUsage in LabAWS Cost
AWS STS (AssumeRole API)Dynamic Token Generation$0.00 (100% Free)
AWS IAM Roles & Trust PoliciesTemporary Access Delegation$0.00 (100% Free)
Total Estimated CostComplete Hands-On Session$0.00 (100% Free)

Core Concept: Static Keys vs Temporary Credentials

Let’s look at the credential prefixes emitted by AWS authentication APIs:

Credential TypePrefixNatureLifetimeStored In
Static IAM User KeyAKIA...Long-lived, permanentForever (until revoked)~/.aws/credentials
Temporary Session KeyASIA...Short-lived, dynamic15 minutes – 12 hoursSession Token / Memory Cache

When you use an IAM Role, AWS never issues an AKIA key. Instead, the AWS Security Token Service (STS) issues an ephemeral ASIA access key, a companion secret key, and a cryptographically signed Session Token.

+-----------------------------------------------------------------------------------+
|                        AWS STS Role Assumption Workflow                           |
+-----------------------------------------------------------------------------------+
                                           |
  1. Base User Auth (AKIA...) + MFA Code   |
  ---------------------------------------->|
                                           v
                        +------------------------------------+
                        | AWS Security Token Service (STS)   |
                        | -> Validates caller identity       |
                        | -> Evaluates Role Trust Policy     |
                        | -> Verifies MFA TOTP code          |
                        +------------------------------------+
                                           |
  2. Issues Temporary Credentials (ASIA...) |
  <----------------------------------------+
  - AccessKeyId: ASIAXXXXXXXXXXXXXXXX               (Valid for 1 hour)
  - SecretAccessKey: wJalrXUtnFEMI/...
  - SessionToken: AQoDYXdzEJr1...

Core Concept: The Two Halves of an IAM Role

Unlike an IAM User (which represents a physical person or static service), an IAM Role is an assumed identity with no permanent credentials. Every IAM Role is defined by two distinct policy documents:

+--------------------------------------------------------------------+
|                             IAM Role                               |
|                                                                    |
|  [ 1. Trust Policy (AssumeRolePolicyDocument) ]                     |
|     -> Question: "WHO is allowed to assume this role?"             |
|     -> Example: Trust user 'engineer-bob' + Require active MFA     |
|                                                                    |
|  [ 2. Permission Policy (Identity-based Policy) ]                  |
|     -> Question: "WHAT is this role allowed to do once assumed?"   |
|     -> Example: Manage EC2 instances, read S3 buckets              |
+--------------------------------------------------------------------+
  1. The Trust Policy (AssumeRolePolicyDocument): Specifies the trusted principals (users, services, or external accounts) permitted to call sts:AssumeRole.
  2. The Permission Policy: Defines the specific AWS API actions and resources the assumed identity can interact with.

Core Concept: IAM Groups vs IAM Roles (The Enterprise Mental Model)

In Part 1, we used an IAM Group (Administrators) to organize users. Now in Part 2, we introduce IAM Roles.

A common question every cloud engineer asks is: “When do I use a Group, and when do I use a Role?”

The fundamental distinction is simple:

  • A Group is for ORGANIZING HUMANS (who they are).
  • A Role is for GRANTING TEMPORARY HATS (what they are doing right now).
+-----------------------------------------------------------------------------------+
|                        Group vs Role Architecture Pipeline                        |
+-----------------------------------------------------------------------------------+

 [ Bob ] ---> joins ---> [ 'Developers' Group ]
                               |
                               | (Baseline: Self-service password & MFA reset only;
                               |  Zero standing infrastructure privileges)
                               v
                       [ 'CloudStagingDeployer' Role ]
                               |
                               | (Assumed dynamically with MFA for 1 hour)
                               v
                     [ 200 OK: Deploy Staging Infrastructure! ]

Key Differences & Decision Framework:

Scenario / RequirementUse Group or Role?Technical Reason
Human User BaselineIAM GroupOrganize team members (e.g. Developers) with baseline self-service policies.
Infrastructure ModificationsIAM RoleRequires temporary, audited, short-lived STS credentials (ASIA...) with MFA.
Server / Compute AccessIAM RoleEC2 instances, ECS tasks, and Lambda functions cannot hold permanent passwords or join groups; they assume roles directly.
Cross-Account AccessIAM RoleIAM Groups cannot cross AWS account boundaries; IAM Roles can be assumed across accounts.
3rd-Party SaaS / CI/CDIAM RolePlatforms like GitHub Actions or Datadog assume roles via OIDC/ExternalId without storing static AWS keys.

Core Concept: Enterprise Separation of Duties

In production engineering organizations, identity management adheres to a strict Separation of Duties:

+-----------------------------------------------------------------------------------+
|                        Enterprise Separation of Duties                            |
+-----------------------------------------------------------------------------------+

 [ Platform / SecOps Team ]               [ Software Developer ]
 (The Cloud Administrators)               (The Role Consumer: Bob)
             |                                       |
             | 1. Provisions 'StagingDeployer' Role  |
             | 2. Attaches Trust & Permission Docs   |
             | 3. Shares Role ARN with Developer     |
             |-------------------------------------->|
             |                                       | 4. Adds Profile to ~/.aws/config
             |                                       | 5. Assumes Role via CLI with MFA
             |                                       |    (Zero permanent keys!)

1. In an Enterprise Organization:

  • The Platform / SecOps Team (The Gatekeepers): Use Infrastructure as Code (Terraform, AWS CDK) to provision accounts, write trust policies, and attach least-privilege permission boundaries.
  • The Software Engineers (The Consumers): Never create their own roles or policies (preventing privilege escalation). They are assigned an unprivileged base identity and a Role ARN, assuming permissions dynamically via MFA.

2. In Our Hands-On Simulation:

Because we are working in our personal AWS environment, we will wear two hats:

  • Hat 1 (The Administrator - cloud-dev-admin): Provisions users, creates CloudStagingDeployer, and configures security policies.
  • Hat 2 (The Engineer - engineer-bob): Operates with zero baseline permissions, dynamically assuming the staging role through the CLI.

Core Concept: Decoding the Amazon Resource Name (ARN)

Throughout AWS CLI commands and policies, every resource is addressed by its Amazon Resource Name (ARN):

arn : partition : service : region : account-id : resource-type / resource-id
 │       │         │        │          │                │            │
 1       2         3        4          5                6            7

The 6 Colon-Separated Fields:

  1. arn: Universal literal prefix.
  2. partition: Cloud boundary (aws for Standard Commercial, aws-cn for China, aws-us-gov for GovCloud).
  3. service: AWS service namespace (iam, sts, s3, ec2, lambda).
  4. region: Geographical region (e.g. ap-southeast-1). Left blank (::) for global services like IAM.
  5. account-id: The 12-digit AWS Account ID owning the resource (e.g. 123456789012).
    • Note: Root, cloud-dev-admin, and engineer-bob all share the exact same Account ID because they reside in the same account container.
    • AWS-Managed Policies: Use aws instead of an account number (arn:aws:iam::aws:policy/AdministratorAccess).
    • S3 Buckets: Omit the account ID entirely (arn:aws:s3:::my-bucket-name) as bucket names are globally unique.
  6. resource-type & resource-id: Specific asset path (user/engineer-bob, role/CloudStagingDeployer, mfa/engineer-bob-mfa).

ARN Comparison Reference:

EntityExample ARNPurpose
Root Identityarn:aws:iam::123456789012:rootMaster account owner
Admin Userarn:aws:iam::123456789012:user/cloud-dev-adminAdministrator identity from Part 1
Engineer Userarn:aws:iam::123456789012:user/engineer-bobZero-privilege developer user
IAM Rolearn:aws:iam::123456789012:role/CloudStagingDeployerTarget role to assume
Virtual MFAarn:aws:iam::123456789012:mfa/engineer-bob-mfaVirtual TOTP authenticator device
Managed Policyarn:aws:iam::aws:policy/AmazonEC2FullAccessAWS-maintained permission document
Assumed Role Sessionarn:aws:sts::123456789012:assumed-role/CloudStagingDeployer/dev-sessionEphemeral session issued by STS

Phase 1: Provisioning the Zero-Privilege Engineer (engineer-bob)

Let’s begin by acting as the Administrator (cloud-dev-admin) to provision a Developers group and our engineer user: engineer-bob.

Step 1: Create the Developers Group & User via Admin CLI

# 1. Create the Developers IAM Group
aws iam create-group \
  --group-name Developers \
  --profile cloud-dev-admin

# 2. Create the User
aws iam create-user \
  --user-name engineer-bob \
  --profile cloud-dev-admin

# 3. Add Bob to the Developers Group
aws iam add-user-to-group \
  --user-name engineer-bob \
  --group-name Developers \
  --profile cloud-dev-admin

Step 2: Generate Access Keys for Bob

aws iam create-access-key \
  --user-name engineer-bob \
  --profile cloud-dev-admin \
  --output json

Output:

{
    "AccessKey": {
        "UserName": "engineer-bob",
        "AccessKeyId": "AKIAEXAMPLEBOBKEYID",
        "Status": "Active",
        "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYBOBSECRETKEY",
        "CreateDate": "2026-08-28T04:00:00+00:00"
    }
}

Step 3: Configure Bob’s Profile Locally

Add Bob’s credentials to your local AWS configuration:

aws configure set aws_access_key_id "AKIAEXAMPLEBOBKEYID" --profile engineer-bob
aws configure set aws_secret_access_key "wJalrXUtnFEMI/K7MDENG/bPxRfiCYBOBSECRETKEY" --profile engineer-bob
aws configure set region "ap-southeast-1" --profile engineer-bob
aws configure set output "json" --profile engineer-bob

Step 4: The “Zero Standing Privilege” Test

Now switch to Bob’s persona. What happens if Bob tries to access AWS resources directly using his static key?

# Attempt to list EC2 instances as Bob
aws ec2 describe-instances --profile engineer-bob

Result: 403 Access Denied!

An error occurred (UnauthorizedOperation) when calling the DescribeInstances operation: 
You are not authorized to perform this operation. User: arn:aws:iam::123456789012:user/engineer-bob 
is not authorized to perform: ec2:DescribeInstances because no identity-based policy allows the ec2:DescribeInstances action
# Attempt to list S3 buckets as Bob
aws s3 ls --profile engineer-bob

Result: 403 Access Denied!

An error occurred (AccessDenied) when calling the ListBuckets operation: User: arn:aws:iam::123456789012:user/engineer-bob is not authorized to perform: s3:ListAllMyBuckets because no identity-based policy allows the s3:ListAllMyBuckets action

This confirms the Zero Standing Privilege model: even if Bob’s static credentials leak, they grant zero direct access to infrastructure.


Phase 2: Creating the CloudStagingDeployer IAM Role

Now, act as the Administrator (cloud-dev-admin) to create the scoped deployment role that Bob is authorized to assume.

Step 1: Retrieve Your Account ID

ACCOUNT_ID=$(aws sts get-caller-identity --profile cloud-dev-admin --query "Account" --output text)

Step 2: Create the Trust Policy JSON

Create staging-trust-policy.json specifying that only engineer-bob can assume this role:

cat <<EOF > staging-trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::${ACCOUNT_ID}:user/engineer-bob"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

[!WARNING] AWS Architectural Rule: IAM Groups are NOT Principals You might wonder: Can we put "arn:aws:iam::${ACCOUNT_ID}:group/Developers" inside the Principal field instead of Bob’s individual user ARN? No. In AWS IAM, an IAM Group is a management container for identity policies, but it is never a valid Principal. Trust policies only accept IAM Users, IAM Roles, AWS Account Roots, or AWS Service Principals.

[!TIP] Cross-Account SaaS & sts:ExternalId (The Confused Deputy Solution): If you create a role intended for a 3rd-party SaaS platform (e.g. Datadog, GitHub Actions, Terraform Cloud) to assume into your account, AWS best practice mandates adding a unique sts:ExternalId condition:

"Condition": { "StringEquals": { "sts:ExternalId": "my-secret-org-uuid" } }

This prevents the Confused Deputy Attack, ensuring another client of that SaaS cannot trick the vendor into assuming your role.


Step 3: Provision the Role via CLI

aws iam create-role \
  --role-name CloudStagingDeployer \
  --assume-role-policy-document file://staging-trust-policy.json \
  --description "Role for deploying staging infrastructure" \
  --profile cloud-dev-admin

Verifying & Listing IAM Roles

List IAM roles to confirm the role was created:

# Query specifically for CloudStagingDeployer:
aws iam list-roles \
  --query "Roles[?RoleName=='CloudStagingDeployer'].[RoleName, RoleId, Arn]" \
  --output table \
  --profile cloud-dev-admin

Output:

---------------------------------------------------------------------------------------------------------
|                                               ListRoles                                               |
+----------------------+----------------------+---------------------------------------------------------+
|  CloudStagingDeployer|  AROA4EXAMPLEUNIQUEID|  arn:aws:iam::123456789012:role/CloudStagingDeployer    |
+----------------------+----------------------+---------------------------------------------------------+

You can also inspect the full role definition and trust policy at any time with aws iam get-role:

aws iam get-role \
  --role-name CloudStagingDeployer \
  --profile cloud-dev-admin \
  --output json

Step 4: Attach Permission Policies to the Role

Attach scoped permissions to manage EC2 instances and read S3 buckets:

# Attach AmazonEC2FullAccess
aws iam attach-role-policy \
  --role-name CloudStagingDeployer \
  --policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess \
  --profile cloud-dev-admin

# Attach AmazonS3ReadOnlyAccess
aws iam attach-role-policy \
  --role-name CloudStagingDeployer \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
  --profile cloud-dev-admin

Verify the attached policies:

aws iam list-attached-role-policies \
  --role-name CloudStagingDeployer \
  --profile cloud-dev-admin \
  --output table

Phase 3: Manual Role Assumption with aws sts assume-role

Now, let’s switch to Bob’s perspective and perform a manual role assumption.

Step 1: Call sts assume-role as Bob

aws sts assume-role \
  --role-arn "arn:aws:iam::${ACCOUNT_ID}:role/CloudStagingDeployer" \
  --role-session-name "bob-staging-session" \
  --duration-seconds 3600 \
  --profile engineer-bob

STS JSON Response:

{
    "Credentials": {
        "AccessKeyId": "ASIA4EXAMPLEKEYID123",
        "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
        "SessionToken": "AQoDYXdzEJr1EXAMPLE...very-long-base64-token...",
        "Expiration": "2026-08-28T05:15:00+00:00"
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "AROA4EXAMPLEUNIQUEID:bob-staging-session",
        "Arn": "arn:aws:sts::123456789012:assumed-role/CloudStagingDeployer/bob-staging-session"
    }
}

Notice the prefix: ASIA.... This indicates a temporary credential accompanied by a cryptographic SessionToken.


Step 2: Export Temporary Environment Variables & Test

Export these temporary credentials and target region into your current shell session:

export AWS_ACCESS_KEY_ID="ASIA4EXAMPLEKEYID123"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_SESSION_TOKEN="AQoDYXdzEJr1EXAMPLE...very-long-base64-token..."
export AWS_DEFAULT_REGION="ap-southeast-1"

[!NOTE] We also export AWS_DEFAULT_REGION (e.g. ap-southeast-1) so regional services like EC2 know which data center to query without throwing a (NoRegion): You must specify a region error.

Re-run the exact EC2 command that previously failed:

aws ec2 describe-instances --output table

Result: 200 OK! The command succeeds because your active caller identity is now assumed-role/CloudStagingDeployer/bob-staging-session.

To revert back to Bob’s baseline unprivileged identity, unset the session variables:

unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_DEFAULT_REGION

Phase 4: Setting Up Virtual MFA for Bob via AWS CLI

In production environments, role assumption should require Multi-Factor Authentication (MFA). Let’s create and enable a Virtual MFA device for engineer-bob directly from the command line.

Step 1: Create the Virtual MFA Device

As the Admin:

aws iam create-virtual-mfa-device \
  --virtual-mfa-device-name "engineer-bob-mfa" \
  --outfile ./bob-mfa-qr.png \
  --bootstrap-method "QRCodePNG" \
  --profile cloud-dev-admin

This outputs:

  1. An MFA device ARN: arn:aws:iam::123456789012:mfa/engineer-bob-mfa
  2. A QR Code image: ./bob-mfa-qr.png

Open and scan the QR code with your authenticator app (1Password, Google Authenticator, Authy):

# On macOS:
open ./bob-mfa-qr.png

Step 2: Enable the MFA Device for Bob

Submit two consecutive 6-digit TOTP codes (30 seconds apart) to synchronize the device:

MFA_ARN="arn:aws:iam::${ACCOUNT_ID}:mfa/engineer-bob-mfa"

aws iam enable-mfa-device \
  --user-name engineer-bob \
  --serial-number "$MFA_ARN" \
  --authentication-code1 123456 \
  --authentication-code2 654321 \
  --profile cloud-dev-admin

Delete the QR code image for security:

rm -f ./bob-mfa-qr.png

Verify the active MFA device:

aws iam list-mfa-devices --user-name engineer-bob --profile cloud-dev-admin --output table

[!NOTE] Who Scans and Who Enables MFA in Real Life?

  • Who scans the QR code? Bob does. The QR code contains the secret seed that must live inside Bob’s personal authenticator app (on his phone or laptop).
  • Why did the Admin run enable-mfa-device? In our simulation, Bob starts with zero permissions (preventing him from touching IAM). The Admin performs the initial device registration using Bob’s two verification codes.
  • In Enterprise Production: Organizations either attach a scoped self-service policy (AllowSelfManageMFA) allowing developers to create and activate their own MFA device (arn:aws:iam::...:mfa/${aws:username}), or enforce MFA centrally through Corporate SSO (Okta / Google Workspace). We will build this exact self-service policy in Part 3!

Phase 5: Hardening the Role Trust Policy with MFA Enforcement

Now, let’s enforce that no one (including Bob) can assume CloudStagingDeployer without presenting a valid MFA token.

Step 1: Update the Trust Policy with Condition

Create mfa-enforced-trust-policy.json:

cat <<EOF > mfa-enforced-trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::${ACCOUNT_ID}:user/engineer-bob"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "Bool": {
          "aws:MultiFactorAuthPresent": "true"
        }
      }
    }
  ]
}
EOF

Step 2: Apply the Hardened Policy via CLI

aws iam update-assume-role-policy \
  --role-name CloudStagingDeployer \
  --policy-document file://mfa-enforced-trust-policy.json \
  --profile cloud-dev-admin

Clean up local JSON files:

rm -f staging-trust-policy.json mfa-enforced-trust-policy.json

If Bob attempts to assume the role without an MFA token:

aws sts assume-role \
  --role-arn "arn:aws:iam::${ACCOUNT_ID}:role/CloudStagingDeployer" \
  --role-session-name "test-session" \
  --profile engineer-bob

Result: Access Denied!

An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:iam::123456789012:user/engineer-bob is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/CloudStagingDeployer

Because the request was sent without an MFA token, the aws:MultiFactorAuthPresent: true condition evaluates to false, causing IAM to block role assumption via an implicit deny.


Step 3: Assume the Role Manually with MFA

To successfully assume the role now, Bob must supply his MFA Device ARN (--serial-number) and his current 6-digit TOTP code (--token-code):

MFA_ARN="arn:aws:iam::${ACCOUNT_ID}:mfa/engineer-bob-mfa"

# Replace 123456 with your current 6-digit code from your authenticator app
aws sts assume-role \
  --role-arn "arn:aws:iam::${ACCOUNT_ID}:role/CloudStagingDeployer" \
  --role-session-name "bob-mfa-session" \
  --serial-number "$MFA_ARN" \
  --token-code 123456 \
  --profile engineer-bob

STS JSON Response (Success!):

{
    "Credentials": {
        "AccessKeyId": "ASIA4EXAMPLEKEYID123",
        "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
        "SessionToken": "AQoDYXdzEJr1EXAMPLE...very-long-base64-token...",
        "Expiration": "2026-08-28T05:15:00+00:00"
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "AROA4EXAMPLEUNIQUEID:bob-mfa-session",
        "Arn": "arn:aws:sts::123456789012:assumed-role/CloudStagingDeployer/bob-mfa-session"
    }
}

Because STS verified the MFA token, the aws:MultiFactorAuthPresent: true condition evaluates to true and AWS issues temporary ASIA... credentials.


Phase 6: Automated Profile Chaining (Zero-Friction Workflow)

Manually exporting environment variables is tedious. The AWS CLI supports Profile Chaining, which automates the entire STS token exchange, MFA prompt, and credential caching.

Step 1: Configure ~/.aws/config

Open ~/.aws/config and configure Bob’s profile chaining:

[profile engineer-bob]
region = ap-southeast-1
output = json

[profile staging]
role_arn = arn:aws:iam::123456789012:role/CloudStagingDeployer
source_profile = engineer-bob
mfa_serial = arn:aws:iam::123456789012:mfa/engineer-bob-mfa
region = ap-southeast-1
output = table

[!NOTE] Replace 123456789012 with your actual AWS Account ID.


Step 2: Execute Commands as the Assumed Role

Now, run any command using --profile staging:

aws ec2 describe-instances --profile staging

The CLI automatically prompts on your terminal:

Enter MFA code for arn:aws:iam::123456789012:mfa/engineer-bob-mfa: 582194

Enter your current 6-digit TOTP code. The command executes seamlessly!

For the next 1 hour, the AWS CLI caches your temporary session token locally. Subsequent commands execute without prompting for MFA until the session expires.


Phase 7: Managing the CLI Credential Cache

Where does the AWS CLI store these temporary session tokens?

Inspect your local cache directory:

ls -la ~/.aws/cli/cache/

You will see SHA-hashed JSON files (e.g. a1b2c3d4e5f6...json).

If you inspect one of these cache files:

cat ~/.aws/cli/cache/*.json | head -n 15

You will see:

{
  "Credentials": {
    "AccessKeyId": "ASIA...",
    "SecretAccessKey": "...",
    "SessionToken": "...",
    "Expiration": "2026-08-28T05:15:00Z"
  },
  "ProviderType": "assume-role"
}

How to Force Invalidate / Flush Cached Sessions:

To test MFA prompts or invalidate an active assumed session immediately:

rm -rf ~/.aws/cli/cache/*

Quick Reference: Essential STS & Role CLI Commands

ActionCLI Command
Check Current Identityaws sts get-caller-identity
Create IAM Groupaws iam create-group --group-name <name>
Add User to Groupaws iam add-user-to-group --user-name <user> --group-name <group>
Assume Role (Manual)aws sts assume-role --role-arn <arn> --role-session-name <name>
Assume Role with MFAaws sts assume-role --role-arn <arn> --role-session-name <name> --serial-number <mfa-arn> --token-code <code>
Create IAM Roleaws iam create-role --role-name <name> --assume-role-policy-document file://trust.json
List IAM Rolesaws iam list-roles --output table
Get Role Detailsaws iam get-role --role-name <name> --output json
Update Trust Policyaws iam update-assume-role-policy --role-name <name> --policy-document file://trust.json
Attach Policy to Roleaws iam attach-role-policy --role-name <name> --policy-arn <arn>
Create Virtual MFAaws iam create-virtual-mfa-device --virtual-mfa-device-name <name> --outfile ./qr.png --bootstrap-method QRCodePNG
Enable MFA Deviceaws iam enable-mfa-device --user-name <user> --serial-number <arn> --authentication-code1 <c1> --authentication-code2 <c2>

Summary & What’s Next

In this second installment of AWS with CLI, we moved to enterprise-level access security:

  1. We replaced dangerous static keys with short-lived STS session credentials (ASIA...).
  2. We demonstrated the Zero Standing Privilege model using engineer-bob.
  3. We provisioned an IAM Role (CloudStagingDeployer) and attached scoped permissions via the CLI.
  4. We created a Virtual MFA device and hardened role assumption with the aws:MultiFactorAuthPresent condition.
  5. We set up Automated Profile Chaining in ~/.aws/config for seamless, cached MFA role switching directly on our terminal.

In Part 3, we will dive into Crafting Granular IAM Policies, Boundaries & Service Control Policies via CLI, writing precision JSON policies from scratch and validating them with the IAM Policy Simulator.

Related & Recommended Guides

Continue exploring related systems architectures and engineering field notes.