The AWS CLI v2
The aws command is how you will touch AWS on call: from your laptop, from a jump box, from a CI job. It is also where most "it works on my machine" incidents start, because the CLI picks its credentials and its Region from half a dozen places, silently. This lesson installs it the way AWS recommends, configures it, and then takes the credential chain apart so that you always know who you are when you press Enter.
Need to know: install CLI v2 with AWS's own installer (/usr/local/bin/aws). aws configure writes keys to ~/.aws/credentials and settings to ~/.aws/config (named profiles are [name] in the first and [profile name] in the second). The CLI looks for credentials in a fixed order and environment variables beat AWS_PROFILE. aws sts get-caller-identity is whoami: run it before anything that matters.
Installing it on Ubuntu 26.04 (arm64)
There are three ways to get aws on this box, and they give you different versions:
| How | Version on 26.04 | Notes |
|---|---|---|
AWS's installer (awscli-exe-linux-aarch64.zip) | 2.37.10 (the current release) | Bundles its own Python and OpenSSL; installs to /usr/local/aws-cli, links /usr/local/bin/aws |
sudo apt install awscli | 2.31.35 | Debian's packaging of v2, built on the system Python; months behind upstream |
sudo snap install aws-cli --classic | follows upstream | A snap; what Ubuntu's command-not-found suggests first |
AWS documents and supports the installer, so the lab uses it. (pip install awscli gives you the old v1 CLI - a different program with different defaults. If aws --version starts with aws-cli/1., that is what you have.)
$ aws --version
Command 'aws' not found, but can be installed with:
sudo apt install awscli
$ cd ~
$ curl -sS "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o awscliv2.zip
$ unzip -q awscliv2.zip
$ ls aws
README.md THIRD_PARTY_LICENSES dist install
$ sudo ./aws/install
You can now run: /usr/local/bin/aws --version
$ aws --version
aws-cli/2.37.10 Python/3.14.6 Linux/7.0.0-31-generic exe/aarch64.ubuntu.26
The version line tells you everything about the binary: CLI version, the bundled Python, the kernel, and exe/aarch64 = the official installer build for arm64. Where it went:
$ which aws
/usr/local/bin/aws
$ readlink -f /usr/local/bin/aws
/usr/local/aws-cli/v2/2.37.10/dist/aws
$ sudo ./aws/install
Found preexisting AWS CLI installation: /usr/local/aws-cli/v2/current. Please rerun install script with --update flag.
$ sudo ./aws/install --update
You can now run: /usr/local/bin/aws --version
The installer refuses to overwrite an existing install unless you say --update - that is how you upgrade later (download the new zip, unzip, sudo ./aws/install --update). AWS also publishes a PGP signature (awscliv2.sig) for the zip; checking it with gpg --verify against AWS's public key is how a careful team proves the download is genuine.
Configuring it: keys, Region, output
aws configure asks four questions and writes the answers to two files:
$ aws configure
AWS Access Key ID [None]: AKIAQ3EGPLAB4LEARNER
AWS Secret Access Key [None]: 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
Default region name [None]: eu-central-1
Default output format [None]: json
Run it again and it shows the current values (the keys masked) in the brackets; Enter keeps them. For scripts there is a non-interactive form, one setting at a time:
$ cd ~/oncall-lab/labs/aws
$ aws configure set aws_access_key_id $(awk '/Access key ID/ {print $NF}' welcome.txt)
$ aws configure set aws_secret_access_key $(awk '/Secret access key/ {print $NF}' welcome.txt)
$ aws configure set region eu-central-1
$ ls -l ~/.aws
total 8
-rw------- 1 learner learner 32 Sep 22 20:00 config
-rw------- 1 learner learner 116 Sep 22 20:00 credentials
$ cat ~/.aws/config
[default]
region = eu-central-1
$ aws sts get-caller-identity
{
"UserId": "AIDAEVXTZTVCSMBGJ2WDW",
"Account": "111122223333",
"Arn": "arn:aws:iam::111122223333:user/learner"
}
Two files, two jobs:
~/.aws/credentialsholds secrets only:aws_access_key_id,aws_secret_access_key,aws_session_token. Mode 600, never in git, never in a Docker image.~/.aws/configholds everything else:region,output,role_arn,sso_session,cli_pager... It may also hold keys, but the credentials file wins.
aws sts get-caller-identity answers "who am I?" with the account and the ARN of the principal the CLI signed as. It needs no permission at all - it works even for a principal that is denied everything - so it is always the first command when something is denied.
Profiles
A profile is a named set of credentials and settings. The default profile is used when you name none; others you pick with --profile NAME or export AWS_PROFILE=NAME. The section names differ between the files, a classic typo source:
~/.aws/credentials ~/.aws/config
[default] [default]
[auditor] [profile auditor]
$ aws configure set aws_access_key_id $(awk -F= '/KEY_ID/ {print $2}' try/auditor.env) --profile auditor
$ aws configure set aws_secret_access_key $(awk -F= '/SECRET/ {print $2}' try/auditor.env) --profile auditor
$ aws configure set region eu-central-1 --profile auditor
$ aws configure list-profiles
default
auditor
$ aws sts get-caller-identity --profile auditor --query Arn --output text
arn:aws:iam::111122223333:user/try-auditor
$ AWS_PROFILE=auditor aws sts get-caller-identity --query Arn --output text
arn:aws:iam::111122223333:user/try-auditor
$ aws configure get region --profile auditor
eu-central-1
The credential chain
When you run a command, the CLI walks this list and stops at the first place that has credentials (botocore's provider chain):
--profileon the command line - which also switches the environment keys off;- the environment:
AWS_ACCESS_KEY_ID+AWS_SECRET_ACCESS_KEY(+AWS_SESSION_TOKEN); - the profile (
AWS_PROFILE, elsedefault):role_arn(assume a role) orsso_session(IAM Identity Center), then its keys in~/.aws/credentials, then keys in~/.aws/config, thencredential_process; - container credentials (ECS tasks, EKS Pod Identity);
- the EC2 instance's role, from the instance metadata service.
Step 2 before step 3 is the trap: an export AWS_ACCESS_KEY_ID=... forgotten in a shell (or in ~/.bashrc, or left by a script you sourced) silently beats every AWS_PROFILE you set:
$ source try/auditor.env
$ AWS_PROFILE=default aws sts get-caller-identity --query Arn --output text
arn:aws:iam::111122223333:user/try-auditor
$ aws configure list
NAME : VALUE : TYPE : LOCATION
profile : <not set> : None : None
access_key : ****************PGKK : env :
secret_key : ****************Jz/g : env :
region : eu-central-1 : config-file : ~/.aws/config
$ aws sts get-caller-identity --profile default --query Arn --output text
arn:aws:iam::111122223333:user/learner
$ unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY
$ aws sts get-caller-identity --query Arn --output text
arn:aws:iam::111122223333:user/learner
aws configure list is the tool for this: the TYPE column says where each value came from (env, shared-credentials-file, config-file, assume-role, sso, iam-role) and LOCATION which variable or file. With --profile the CLI logs "Skipping environment variable credential check because profile name was explicitly set" - visible with --debug.
The Region
The Region is resolved separately, in this order: --region, AWS_REGION, AWS_DEFAULT_REGION, the profile's region. With none of them, a regional service refuses:
$ AWS_REGION=eu-west-1 aws configure list
NAME : VALUE : TYPE : LOCATION
profile : <not set> : None : None
access_key : ****************RNER : shared-credentials-file :
secret_key : ****************R2Kq : shared-credentials-file :
region : eu-west-1 : env : AWS_REGION
$ aws kms list-keys --profile auditor --region eu-west-1 --query 'length(Keys)'
0
$ aws configure set region '' --profile auditor
$ aws kms list-keys --profile auditor
aws: [ERROR]: You must specify a region. You can also configure your region by running "aws configure".
$ echo $?
253
$ aws configure set region eu-central-1 --profile auditor
Exit code 253 is the CLI's "your configuration is incomplete" - no request was even sent. IAM and STS work without a Region (IAM is global; STS falls back to its global endpoint), which is why get-caller-identity succeeding proves nothing about your Region.
People sign in, they do not carry keys
Long-lived access keys for humans are the thing AWS's own guidance tells you to get rid of. In a real organization people use IAM Identity Center: one sign-in (often through Entra ID or Okta), then a choice of account and permission set, and the CLI gets credentials that expire after a few hours:
$ aws configure sso
SSO session name (Recommended): oncall-lab
SSO start URL [None]: https://oncall-lab.awsapps.com/start
SSO region [None]: eu-central-1
SSO registration scopes [sso:account:access]:
...
The only AWS account available to you is: 111122223333
Using the account ID 111122223333
There are 2 roles available to you.
> AdministratorAccess
ReadOnlyAccess
...
Profile name [AdministratorAccess-111122223333]: lab-admin
$ aws sso login --profile lab-admin --use-device-code
The profile gets sso_session, sso_account_id and sso_role_name instead of keys; aws sso login refreshes the session (--use-device-code on a machine without a browser: you approve the code on your laptop). The role you land in is AWSReservedSSO_<permission set>_<id>, which is what CloudTrail and AccessDenied messages will show. Since CLI 2.32 there is also aws login, which hands the CLI temporary credentials from your AWS console session (it needs a browser, so it is for laptops, not servers).
In an interview: "How does the CLI decide which credentials to use?" - "It walks a chain: --profile, then the environment variables, then the profile's role, SSO or keys from the credentials and config files, then container and instance-role credentials. The first hit wins.
aws configure listshows which one won, andaws sts get-caller-identityshows who that is."
You can now: install and upgrade CLI v2 on arm64 Ubuntu, configure keys and Regions in the right file, use named profiles, predict which credentials the chain picks (and catch an environment variable overriding your profile), and explain why people should use IAM Identity Center instead of access keys.