Skip to content

Credentials

VPC Atlas reads credentials the way the AWS CLI does. It uses the AWS SDK for Python (boto3), so every method the SDK supports works without any option: environment variables, the shared ~/.aws/credentials and ~/.aws/config files, SSO profiles, and the roles that come with EC2, ECS, EKS and Lambda. On top of that it can assume a role, with an external ID and MFA, and can scan several accounts into one graph.

Secret keys are never command line flags. A flag ends up in your shell history and in the process list. Access keys come in through the standard credential chain only.

Pick a method

You have Use Section
Access keys in the environment nothing, the default chain finds them Environment variables
A profile in ~/.aws --profile NAME Profiles
SSO (IAM Identity Center) aws sso login, then --profile NAME SSO
A role in the target account --role-arn ARN Roles
A role that needs an external ID --role-arn ARN --external-id ID Roles
A role that needs MFA --mfa-serial ARN --mfa-token CODE MFA
Several accounts repeat --profile or --role-arn Several accounts
An EC2, ECS, EKS or Lambda workload nothing, the runtime role is found Runtime roles
Docker mount ~/.aws or pass AWS_* variables Docker

The options

These options exist on discover, inventory and diagram. serve takes the same ones except the two MFA options.

Option Meaning Environment variable Default
--profile, -p Named AWS profile. Repeat it to scan several accounts. none (the SDK reads AWS_PROFILE) the default chain
--role-arn Role to assume. Repeat it to scan several accounts. none none
--external-id ExternalId the role's trust policy requires. VPCATLAS_EXTERNAL_ID none
--mfa-serial Serial number or ARN of the MFA device. none none
--mfa-token Current one-time code. Must be given together with --mfa-serial. none none
--session-duration Seconds the assumed-role session lasts, 900 to 43200. VPCATLAS_SESSION_DURATION 3600
--role-session-name RoleSessionName, 2 to 64 characters from letters, digits and + = , . @ _ -. VPCATLAS_ROLE_SESSION_NAME vpcatlas

A flag wins over its environment variable. The dashboard also reads VPCATLAS_PROFILE and VPCATLAS_ROLE_ARN; see Configuration.

What happens when it starts

  1. Credentials are resolved once per account: one set per --profile, or the default chain when there is none. With --role-arn, the first --profile (or the default chain) is the single base identity that assumes every role.
  2. With --role-arn, those credentials call sts:AssumeRole for each role, with the session name, duration, external ID and MFA you gave. The role session name shows up in the CloudTrail of the audited account, so vpcatlas is visible there. Change it with --role-session-name if your team wants a ticket number in it.
  3. Assumed-role credentials refresh by themselves before they expire, so a long scan does not fail halfway. The exception is MFA: a one-time code cannot be reused, so with --mfa-serial the session is fixed and must outlast the scan (use --session-duration).
  4. All scanning threads share those credentials.

Access keys through environment variables

The simplest case. Export the standard variables and run:

export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...          # only for temporary credentials
vpcatlas discover --regions eu-west-1

Use it for CI jobs and one-off runs. Prefer short-lived credentials (a session token) over long-lived user keys, and read the values from your secret store instead of typing them into the shell.

Shared credentials and config profiles

Profiles live in ~/.aws/credentials and ~/.aws/config. Select one with --profile:

vpcatlas discover --profile audit

or with the environment variable the SDK reads:

AWS_PROFILE=audit vpcatlas discover

A profile that itself assumes a role (role_arn with source_profile or credential_source, optionally external_id and mfa_serial) keeps working, because vpcatlas lets the SDK resolve it:

# ~/.aws/config
[profile base]
region = eu-west-1

[profile audit]
role_arn = arn:aws:iam::111122223333:role/AtlasAuditor
source_profile = base
external_id = my-external-id

AWS_SHARED_CREDENTIALS_FILE and AWS_CONFIG_FILE point the SDK at other files.

SSO profiles

Sign in with the AWS CLI first, then use the profile:

aws sso login --profile audit
vpcatlas discover --profile audit

vpcatlas reads the cached token that aws sso login writes under ~/.aws/sso/cache. When the SSO session expires the scan stops with exit code 3 and an ExpiredToken or UnauthorizedSSOTokenError style message: run aws sso login again. Inside Docker see the Docker section.

A role in another account, with an external ID

The usual setup for an auditor: you run VPC Atlas with an identity in one account (your own, a hub account or a CI role) and it assumes a read-only role in the account to audit.

vpcatlas discover \
  --role-arn arn:aws:iam::111122223333:role/AtlasAuditor \
  --external-id my-external-id \
  --regions eu-west-1

The identity that calls AssumeRole is the first --profile, or the default chain when there is none:

vpcatlas discover --profile hub \
  --role-arn arn:aws:iam::111122223333:role/AtlasAuditor --external-id my-external-id

For the dashboard container the same values come from the environment:

VPCATLAS_ROLE_ARN=arn:aws:iam::111122223333:role/AtlasAuditor
VPCATLAS_EXTERNAL_ID=my-external-id

The role in the audited account

Attach the auditor policy to the role, and give it a trust policy that names the account (or, better, the exact role or user) that is allowed to assume it and requires the external ID. Here 999988887777 is the account that runs VPC Atlas:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam::999988887777:root" },
      "Action": "sts:AssumeRole",
      "Condition": { "StringEquals": { "sts:ExternalId": "my-external-id" } }
    }
  ]
}

To limit who can assume the role inside the trusted account, replace root with the ARN of one role or user, for example arn:aws:iam::999988887777:role/CiAuditor. The external ID is not a secret in the way a password is; it stops a third party from tricking a trusted account into assuming the role for them (the "confused deputy" problem), so use a long random value and a different one for each audited account.

On the caller's side the identity needs permission to assume the role:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "sts:AssumeRole",
      "Resource": "arn:aws:iam::111122223333:role/AtlasAuditor"
    }
  ]
}

Session length

--session-duration sets how long the assumed credentials live, from 900 to 43200 seconds (default 3600). Two limits come from AWS, not from vpcatlas:

  • The role's own "Maximum session duration" must be at least what you ask for. It is 1 hour unless you raised it, so a request for --session-duration 7200 fails with a ValidationError until the role allows 2 hours or more.
  • When the identity that calls AssumeRole is itself an assumed role (role chaining, for example IRSA on EKS and then --role-arn), AWS caps the session at 1 hour whatever the role allows.
vpcatlas discover --role-arn arn:aws:iam::111122223333:role/AtlasAuditor \
  --session-duration 7200 --role-session-name audit-2026

A role that requires MFA

If the trust policy has aws:MultiFactorAuthPresent, pass the device and the current code:

vpcatlas discover \
  --role-arn arn:aws:iam::111122223333:role/AtlasAuditor \
  --mfa-serial arn:aws:iam::999988887777:mfa/alice \
  --mfa-token 123456

The code is valid for about thirty seconds and is used once, so leaking it through shell history is harmless a minute later. --mfa-serial and --mfa-token go together (giving one without the other is a usage error, exit code 2). serve does not take them, because a dashboard that runs for days cannot use a one-time code. Run discover with MFA, then serve the result with vpcatlas serve --cache vpcatlas-out/vpcatlas-graph.json, or give the dashboard a workload role that does not need MFA.

If the role only requires MFA on the source_profile, a profile with mfa_serial in ~/.aws/config works as for the AWS CLI, but the SDK prompts on a terminal; --mfa-serial and --mfa-token are the non-interactive way.

Several accounts in one graph

Repeat --profile, or repeat --role-arn. The accounts are scanned one after another and merged into a single graph, so a resource that one account shares with another is drawn once, with its real details from the account that owns it, instead of as a stub of an external account.

# One profile per account
vpcatlas discover --profile prod --profile staging --profile network

# One role per account, assumed from one identity
vpcatlas discover --profile hub \
  --role-arn arn:aws:iam::111122223333:role/AtlasAuditor \
  --role-arn arn:aws:iam::444455556666:role/AtlasAuditor \
  --external-id shared-external-id

Rules:

  • Several --profile values and --role-arn together are not accepted. One --profile plus several --role-arn is: the profile is the identity that assumes them.
  • --external-id, --session-duration and --role-session-name apply to every role of the run. If the accounts use different external IDs, run once per account and merge later, or give each role the same ID.
  • With MFA, use one role per run: the code is a one-time value.
  • Both flags take comma separated lists as well: --profile prod,staging.
  • For the dashboard, VPCATLAS_PROFILE and VPCATLAS_ROLE_ARN take comma separated lists.

Roles that come with the runtime

On AWS workloads the credentials are provided by the environment. Run vpcatlas serve or vpcatlas discover with no credential option at all, and add --role-arn only to reach other accounts from that identity.

Where it runs How the credentials arrive What you do
EC2 Instance profile, through the instance metadata service. Attach a role with the auditor policy to the instance.
ECS and Fargate Task role, through the container credentials endpoint (AWS_CONTAINER_CREDENTIALS_RELATIVE_URI). Set taskRoleArn in the task definition.
EKS with IRSA Web identity: AWS_ROLE_ARN and AWS_WEB_IDENTITY_TOKEN_FILE, injected by the service account annotation. Annotate the service account with eks.amazonaws.com/role-arn.
EKS with Pod Identity Container credentials endpoint (AWS_CONTAINER_CREDENTIALS_FULL_URI and an authorization token file). Create a Pod Identity association for the service account.
Lambda Execution role, through AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN set by the service. Give the function's execution role the auditor policy.

EC2, ECS, IRSA and Lambda credentials are found by every SDK version. Pod Identity needs a recent one; the boto3 that vpcatlas requires (1.35 or newer) understands it.

To audit other accounts from there, give the workload role sts:AssumeRole on the target roles and set the roles in the environment:

VPCATLAS_ROLE_ARN=arn:aws:iam::111122223333:role/AtlasAuditor,arn:aws:iam::444455556666:role/AtlasAuditor
VPCATLAS_EXTERNAL_ID=my-external-id

Remember the one hour cap on a chained role session (above): the dashboard re-assumes the roles by itself when they expire, so the cap only matters if you try to set a longer VPCATLAS_SESSION_DURATION.

Complete manifests are in the Deploy section: ECS, Kubernetes and Lambda.

Docker

The image has no AWS CLI and runs as uid 10001 with /home/atlas as home. That shapes how credentials get in.

Mount ~/.aws read-only. This covers access keys in the credentials file and profiles with role_arn and source_profile:

docker run --rm -v "$HOME/.aws:/home/atlas/.aws:ro" -v "$PWD/vpcatlas-out:/data" \
  vpcatlas/vpcatlas discover --profile audit --output-dir /data

On Linux add --user "$(id -u):$(id -g)" -e HOME=/home/atlas, otherwise uid 10001 cannot read your files.

Pass AWS_* variables by name. With no value after the name, Docker copies the variable from your shell, so the secret stays out of the command line:

docker run --rm -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e AWS_REGION \
  -v "$PWD/vpcatlas-out:/data" vpcatlas/vpcatlas discover --output-dir /data

credential_process does not work inside the container, because it runs a helper binary that the image does not contain. An SSO profile works from the token cache in the mounted ~/.aws until that token expires. For a long run, resolve the profile on the host and pass the short-lived result:

aws sso login --profile audit
eval "$(aws configure export-credentials --profile audit --format env)"
docker run --rm -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN \
  -v "$PWD/vpcatlas-out:/data" vpcatlas/vpcatlas discover --output-dir /data

The exported credentials last as long as the SSO or role session does. For a dashboard that must keep running, use a role that the platform provides (ECS, EKS) rather than exported keys.

Roles work normally. --role-arn, --external-id and the VPCATLAS_* variables behave as outside Docker, as long as the base identity can call sts:AssumeRole.

The auditor policy

The role or user that VPC Atlas runs as needs read access only. Two options:

  • The AWS managed policy ReadOnlyAccess.
  • The minimal policy that ships with the tool, which grants only the API calls the collectors make:
vpcatlas iam-policy > auditor-policy.json        # full policy
vpcatlas iam-policy --core-only                  # only the Core* statements, enough for the diagrams

The same file is deploy/iam/auditor-policy.json in the repository. The full policy is too long for a customer managed policy (6,144 characters), so attach it as an inline policy of a role, or use --core-only as a managed policy. A call that is denied is recorded as an issue and skipped; the rest of the scan runs. See IAM permissions for every action and Cross-account for the multi-account patterns.

Coming from another tool

VPC Atlas Prowler Steampipe CloudQuery
Named profile --profile, -p --profile, -p profile in the connection local_profile on an account
Assume a role --role-arn --role, -R role_arn and source_profile in the AWS config profile role_arn on an account
External ID --external-id --external-id, -I external_id in the AWS config profile external_id on an account
Session duration --session-duration --session-duration, -T duration_seconds in the AWS config profile none that we know of
Session name --role-session-name --role-session-name role_session_name in the AWS config profile role_session_name on an account
MFA --mfa-serial and --mfa-token --mfa (prompts for the device and the code) cannot prompt; mfa_serial and a credential_process script none that we know of
Access keys environment and shared files, never flags environment and shared files, no flags access_key, secret_key, session_token in the connection, or environment environment and shared files
Several accounts repeat --profile or --role-arn one --role per run one connection per profile, merged with an aggregator list of accounts
External ID per account one value per run one value per run per profile per account

The names are the point: if you have used --role, --external-id, --session-duration and --role-session-name in Prowler, the same ideas are here under the names in the first column. Default values differ: the session name defaults to vpcatlas, so the audited account's CloudTrail shows who assumed the role.

The one place where VPC Atlas is stricter is secrets: there is no --access-key or --secret-key. Put keys in the environment or a shared credentials file, as you would for the AWS CLI.

When it does not work

A credential or authorization problem ends the run with exit code 3 and a one line message. The common ones:

Message Meaning
Unable to locate credentials The chain found nothing. Export keys, pass --profile, or run on a workload with a role.
ExpiredToken or ExpiredTokenException Temporary credentials ran out. Sign in again or export fresh ones.
InvalidClientTokenId or SignatureDoesNotMatch Wrong or truncated keys, or a typo in the secret.
AccessDenied on AssumeRole The trust policy does not allow the caller, the external ID differs, or MFA is required.
ValidationError on DurationSeconds (exit code 1) The role's maximum session duration is lower than --session-duration, or the session is chained.
The config profile (NAME) could not be found --profile names a profile that does not exist.

More in Troubleshooting.