paulserban.eu

Writing Edition

Paul Serban

Writing, snippets & book notes

August 22, 2019

AWS IAM - Setting Up AWS CLI with Access Keys

Configuring AWS CLI Authentication the Right Way - From Access Keys to Temporary Credentials

Introduction

The AWS Command Line Interface (CLI) is the primary way most engineers interact with AWS outside of infrastructure-as-code tooling - listing resources, debugging a misbehaving service, running one-off scripts, or gluing AWS operations into a shell pipeline. Before any of that works, the CLI needs a way to prove to AWS who is making the request, and that proof comes down to a credential the CLI can find and present with every API call.

For a long time, the default answer to "how do I authenticate the CLI" was: create an IAM user, generate an access key pair, and run aws configure. That still works today and is worth understanding thoroughly, both because it remains common in existing environments and because the credential resolution mechanics underneath it are the same mechanics used by every other authentication method. This article walks through the access key setup in detail, then places it in context against the temporary-credential approaches - IAM roles, AWS SSO / IAM Identity Center - that AWS now recommends as the default for most situations. Understanding both is what lets you make a deliberate choice instead of defaulting to whatever the first tutorial you read happened to use.

Context: Why Credential Setup Matters More Than It Looks

On the surface, configuring the CLI looks like a five-minute chore: generate a key, paste it into a prompt, and move on to the actual work. The trouble is that this credential, once created, is a long-lived bearer secret - anyone who obtains it can act as that IAM user until the key is deactivated, regardless of where or how they obtained it. Unlike a password, there's no username-plus-password prompt to slow down casual misuse, and unlike a browser session, there's no expiration unless someone manually rotates or revokes it.

This is precisely why leaked AWS access keys are one of the most common and well-documented sources of real-world cloud security incidents. A key pasted into a public GitHub repository, left in a Jupyter notebook that gets shared, or embedded in a CI/CD script that gets copied elsewhere can be discovered by automated scanning within minutes - cloud providers and security researchers have both built tooling specifically to detect exposed AWS keys in public repositories, which shows how routinely this happens. Once found, a valid key gives an attacker the same programmatic access as the legitimate user, scoped only by whatever IAM policy was attached.

None of this means access keys should never be used - for a solo developer's local machine, or short-lived testing, they're often the pragmatic choice. What it means is that the setup process deserves more care than "get it working," because the credential you create is exactly as sensitive as a password, and in the CLI's case, one that's easy to accidentally leave lying around in a shell history file or a config directory. Setting it up correctly - and knowing when a different method is a better fit - is a genuine security decision, not just plumbing.

AWS Access Methods Overview

Before configuring the CLI specifically, it helps to place it among the other ways AWS can be accessed, since they all draw from the same underlying identity model. The AWS Management Console is the web interface most people encounter first, authenticated through an IAM user's password (optionally with MFA), a federated role via IAM Identity Center, or - in the case of a role assumed through the console's "Switch Role" feature - a temporary session layered on top of an existing sign-in. It's well suited to exploration, one-off configuration changes, and visual review of resources, but it doesn't scale to automation or bulk operations.

AWS CLI and the various AWS SDKs (boto3 for Python, aws-sdk-js-v3 for JavaScript/TypeScript, and equivalents for Java, Go, .NET, Ruby, and others) sit on the automation side of that divide. Both draw credentials from the same shared configuration and credential resolution chain, which means a CLI profile you configure once is often usable by an SDK-based script without any additional setup. Both support the full range of authentication options - static access keys, assumed roles, and SSO-based temporary credentials - with the practical difference being that the CLI is typically used interactively or in shell scripts, while SDKs are embedded directly into application code. The rest of this article focuses on the CLI, since it's the fastest way to establish and verify a working credential chain before wiring the same setup into application code.

Deep Dive: How AWS CLI Authenticates

When you run any aws command, the CLI resolves credentials by checking a fixed, ordered list of sources and using the first one it finds. Understanding this order matters because it explains a surprisingly common source of confusion - a developer sets a value in ~/.aws/credentials, the CLI ignores it, and it turns out an environment variable set earlier in the shell session is taking precedence without anyone noticing.

The order, from highest to lowest priority, is: command-line options (--profile, explicit flags), environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN), the shared credentials file (~/.aws/credentials), the shared config file (~/.aws/config) when it contains credential information, credential process or SSO configuration referenced from either file, and finally container or instance metadata credentials - the automatically-rotated temporary credentials available to code running on ECS tasks or EC2 instances with an attached IAM role. This chain is why aws configure writes to two separate files rather than one: ~/.aws/credentials holds the actual secret material, while ~/.aws/config holds non-secret settings like the default region and output format, plus profile definitions for anything beyond the default profile.

Named profiles are the mechanism that makes this chain usable across multiple accounts or roles without constantly re-authenticating. A ~/.aws/credentials file can define several [profile-name] sections, each with its own key pair, and any CLI command can target a specific one with --profile profile-name or by setting the AWS_PROFILE environment variable for the session. This is the same mechanism that supports role-based profiles - a profile section can reference a role_arn and a source_profile, telling the CLI to use one set of credentials to call sts:AssumeRole and transparently use the resulting temporary credentials for the actual request, without the caller needing to run aws sts assume-role manually each time.

SSO-based profiles extend this further by removing static secrets from the local filesystem entirely. A profile configured with aws configure sso stores only non-sensitive metadata - the SSO start URL, region, and target account/role - and the actual short-lived credentials are fetched interactively through a browser-based login (aws sso login) and cached locally with an expiration. When the cached session expires, CLI commands fail with a clear re-authentication prompt rather than silently using a stale or compromised long-lived key, which is one of the more concrete security advantages this approach has over static access keys.

Implementation: Setting Up AWS CLI Step-by-Step

Setting up the traditional access-key flow starts in the IAM console, not the terminal. Sign in to the AWS IAM console, navigate to the target IAM user, open the Security credentials tab, and choose Create access key under the Access keys section. AWS will prompt for the key's intended use case (CLI, application code, or third-party service) - this is informational metadata that helps with later auditing rather than a technical restriction - and will then display the access key ID and secret access key exactly once. The secret access key cannot be retrieved again after this screen closes, so it needs to be captured immediately, ideally into a password manager rather than a plain-text file.

With the key pair in hand, aws configure is the fastest path to a working setup:

aws configure
AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Default region name [None]: us-east-1
Default output format [None]: json

This writes the key pair into ~/.aws/credentials under the [default] profile and the region/output preferences into ~/.aws/config. For a setup involving multiple accounts or roles, naming the profile explicitly keeps them separate and auditable:

aws configure --profile staging-readonly

Editing the files directly is equivalent and sometimes more convenient for scripting an initial environment setup - for instance, provisioning a new developer's machine from a configuration management tool:

# ~/.aws/credentials
[staging-readonly]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# ~/.aws/config
[profile staging-readonly]
region = us-east-1
output = json

Whichever method is used, the setup should be verified immediately rather than assumed correct. aws sts get-caller-identity calls the Security Token Service to confirm exactly which identity the CLI is currently authenticating as, which is useful both for initial verification and later for debugging "why is this command hitting the wrong account" issues:

aws sts get-caller-identity --profile staging-readonly
{
  "UserId": "AIDACKCEVSQ6C2EXAMPLE",
  "Account": "123456789012",
  "Arn": "arn:aws:iam::123456789012:user/staging-readonly-user"
}

A returned ARN matching the expected user and account confirms the credential chain is resolving correctly, and is a habit worth keeping any time you switch profiles or hand a configuration to someone else.

Programmatic Patterns Beyond Static Keys

Once a working credential chain exists, it's worth extending the same setup to avoid static keys in application code, since the CLI's ~/.aws/credentials and ~/.aws/config files are read by SDKs using the identical resolution order. In Python, boto3 will automatically pick up a named profile without any explicit key handling in code:

import boto3

# Resolves credentials via the shared config/credentials chain,
# including role assumption if the profile defines role_arn/source_profile
session = boto3.Session(profile_name="staging-readonly")
s3 = session.client("s3")

response = s3.list_buckets()
for bucket in response["Buckets"]:
    print(bucket["Name"])

The equivalent pattern in TypeScript using the AWS SDK for JavaScript v3 uses the fromIni credential provider to read the same shared files, or fromNodeProviderChain to replicate the CLI's full default resolution order (environment variables, shared config, container credentials, and instance metadata) inside application code:

import { S3Client, ListBucketsCommand } from "@aws-sdk/client-s3";
import { fromNodeProviderChain } from "@aws-sdk/credential-providers";

const client = new S3Client({
  region: "us-east-1",
  credentials: fromNodeProviderChain({ profile: "staging-readonly" }),
});

async function listBuckets() {
  const response = await client.send(new ListBucketsCommand({}));
  response.Buckets?.forEach((bucket) => console.log(bucket.Name));
}

listBuckets();

Neither example hardcodes a secret anywhere in the source tree, which means the same code can run unmodified on a developer's laptop (using a named profile), in CI (using environment variables or an OIDC-based role), and in production on ECS or Lambda (using an attached execution role) - the credential source changes, but the application code and the resolution logic it relies on do not.

Trade-offs and Common Pitfalls

The convenience of static access keys is real: they work identically across every AWS SDK and CLI version, require no browser or network round-trip beyond the API call itself, and are trivial to script into automated environment provisioning. That convenience is exactly what makes them risky in the long run - a key configured once tends to stay valid indefinitely unless someone actively rotates or revokes it, and there is no built-in mechanism forcing that to happen. In practice, this means access keys accumulate the same way unused IAM users do: created for a specific task, forgotten once the task is done, and left active as a standing liability.

A handful of specific mistakes account for most of the incidents involving CLI-configured credentials. Committing a .env file or a script containing AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY to a Git repository is the most common one, and it's dangerous even if the repository is later made private, since the credential remains in the commit history and in any forks or local clones already made. Copy-pasting aws configure output into a shared runbook or support ticket is a subtler variant of the same mistake. Reusing a single long-lived IAM user's keys across multiple developers or environments, rather than issuing individually attributable credentials, also undermines the audit trail that aws sts get-caller-identity and CloudTrail are supposed to provide - if three people share one key, there's no way to tell from the logs which of them made a given request.

SSO-based and role-based profiles trade some of that convenience for meaningfully better security properties, but they introduce their own friction: they typically require network access to an identity provider, occasional re-authentication when a cached session expires, and, in more complex multi-account setups, careful configuration of trust relationships between the source and target accounts. For a quick, disposable sandbox account with no sensitive data, that overhead may not be worth it. For anything touching production data or systems, it almost always is, and the setup cost is a reasonable price for removing a standing secret from the equation entirely.

Best Practices for Secure CLI Access

Treat access key generation as an event worth minimizing, not a routine setup step. Before creating a new IAM user and key pair, check whether an existing role-based or SSO-based option would work instead - AWS Identity Center supports CLI access through aws configure sso in exactly the same way access keys support aws configure, with the meaningful difference that the resulting credentials expire automatically. Where an access key genuinely is the right tool, scope the underlying IAM user's policy tightly to what the task requires, rather than attaching a broad managed policy for convenience.

For keys that do need to exist, build rotation into the workflow rather than treating it as an occasional cleanup task: IAM supports having two active access keys per user specifically so that a new key can be created, validated, and rolled into use before the old one is deactivated, avoiding downtime during rotation. Enable IAM Access Analyzer's unused-access findings to surface access keys that haven't been used recently, and pair that with a pre-commit hook or secret-scanning tool in CI to catch a key before it's pushed to a shared repository rather than after. Finally, enable MFA on any IAM user capable of generating or managing access keys in the first place - protecting the console session that creates a key is just as important as protecting the key itself once it exists.

Analogies & Mental Models

Thinking of an access key pair as a spare house key left under the doormat captures both its convenience and its risk fairly precisely. It's fast to set up, it works reliably for whoever has it, and it requires no coordination with anyone else to use - which is exactly why it's also a liability the moment someone other than the intended person finds it. A role-based or SSO credential is closer to a smart lock that issues a temporary code after verifying your identity: slightly more setup and slightly more friction on each use, but the code expires on its own, and there's a record of exactly when and to whom it was issued.

The credential resolution chain is easier to remember as a search through pockets in a fixed order: check your hand first (command-line flags), then your jacket pocket (environment variables), then your bag (the credentials file), then the car (instance or container metadata) - the CLI stops at the first pocket where it finds something, even if a "better" credential is sitting in a pocket it never checks because an earlier one already had a match. This is precisely why a stray AWS_ACCESS_KEY_ID environment variable set in a shell profile months ago can silently override a carefully configured named profile - the CLI isn't misbehaving, it's just checking pockets in the order it always does.

A third mental model worth holding onto is the distinction between a bearer credential and a session. An access key, like a bearer bond, is valid simply by being presented - whoever holds it can use it, with no further check against who they are. A role assumed through STS, or a session established through SSO, works more like a boarding pass tied to a specific, recently-verified identity and a specific window of time: it's still possible to misuse if intercepted during that window, but the window is short and the identity behind it is traceable, which meaningfully limits how much damage a single leak can do.

Key Takeaways

For anyone setting up or auditing AWS CLI access today, these five steps cover most of the practical ground from this article:

Conclusion

Configuring AWS CLI access looks like a small, mechanical step, but the decision embedded in it - static access keys versus temporary, identity-backed credentials - carries real security weight that compounds over the life of an AWS account. The aws configure flow with an IAM access key pair remains a legitimate, well-documented option, and understanding exactly how the CLI resolves credentials, stores them, and verifies them is foundational knowledge whether or not you end up using keys directly.

Where possible, favor the credential types that expire on their own - IAM roles assumed through sts:AssumeRole, or SSO sessions through IAM Identity Center - since they remove the single biggest risk of the access-key approach: a secret that stays valid indefinitely unless someone remembers to rotate it. Where access keys are still the right tool, treat their creation, storage, and rotation with the same discipline you'd apply to any other production credential, and verify the setup with aws sts get-caller-identity rather than assuming it worked.

References

A quick flashcard preview - one example of each question type in this post. This is a demo of what you'll find in the full quiz app.

Flashcard preview - 1 / 9Multiple Choice

multiple choice - beginner - auto-graded

After configuring a new profile, what is the recommended way to confirm which identity the CLI is actually authenticating as?

Choose an answer