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¶
- 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. - With
--role-arn, those credentials callsts:AssumeRolefor 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, sovpcatlasis visible there. Change it with--role-session-nameif your team wants a ticket number in it. - 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-serialthe session is fixed and must outlast the scan (use--session-duration). - 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 7200fails with aValidationErroruntil the role allows 2 hours or more. - When the identity that calls
AssumeRoleis 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
--profilevalues and--role-arntogether are not accepted. One--profileplus several--role-arnis: the profile is the identity that assumes them. --external-id,--session-durationand--role-session-nameapply 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_PROFILEandVPCATLAS_ROLE_ARNtake 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.