Skip to content

Cross-account networks

Real AWS networks cross account borders: a hub account owns the Transit Gateway, workload accounts attach to it, a partner peers a VPC with yours, a platform team shares subnets. vpcatlas follows those links using only what the credentials of the scanned account can see, and it can scan several accounts in one run to fill in the other side.

How linked accounts appear

An account that shows up in the network but is not scanned is an external account. It appears in three places:

  • In the diagrams, as its own dashed group named External account <id> (with the alias when known), outside the audited accounts. The group holds only the stubs of the resources that your accounts refer to: a dashed VPC with its CIDR when the API returned one, or an icon labelled like tgw-0abc... (owner 222222222222, shared by RAM). Edges to stubs use the normal edge styles.
  • In the inventory, in the section "External accounts and cross-account links" (Markdown and HTML) and in external_accounts[] and links[] of the JSON. Each external account lists how it was revealed (seen_via), how many links point to it and which of its resources are referenced. Each link names the resource on your side, the resource on the other side when it is known, the other account and the API call that showed it (detected_by).
  • In the graph file (vpcatlas-graph.json), as nodes of type external_account, and stub nodes with stub: true.

The formats are described in Outputs.

There is no extra API call for any of this. Links come from fields that name an owner in answers the scan makes anyway.

Link What you see Where the other account shows
Transit Gateway shared through RAM The gateway with its owner, and your attachments to it (shared_tgw, tgw_attachment_to_shared_tgw) describe_transit_gateways (owner), describe_transit_gateway_attachments, RAM get_resource_shares (owning account)
Attachments from other accounts to your gateway tgw_attachment_from_other_account describe_transit_gateway_attachments (ResourceOwnerId)
Transit Gateway peering tgw_peering describe_transit_gateway_peering_attachments
VPC peering with a foreign account vpc_peering, with a stub VPC for the peer describe_vpc_peering_connections: the owner of the requester and accepter VPCs
Shared VPC, subnet or network interface shared_subnet and similar, kept and marked as owned elsewhere describe_vpcs, describe_subnets, describe_network_interfaces (owner id)
Shared endpoints and endpoint services The endpoint and the producer of the service describe_vpc_endpoints (service name), describe_vpc_endpoint_connections (endpoint owner)
Route 53 Resolver rules shared with you The rule, with the owner and the share status list_resolver_rules (owner and SHARED_WITH_ME)
Prefix lists and security group references The list or group, and the account in the rule describe_managed_prefix_lists (owner), describe_security_groups (group pairs in rules)
Direct Connect Gateway associations and virtual interfaces owned elsewhere describe_direct_connect_gateway_associations, describe_virtual_interfaces
Network Firewall on a shared Transit Gateway The firewall and the gateway owner describe_firewall (Transit Gateway owner)

Coverage has the same table with the exact calls.

What the tool can and cannot read

It can read:

  • the account id of the other side, in every case above;
  • the resources the scanned account is allowed to describe even though another account owns them: a shared subnet, a shared Transit Gateway, a shared prefix list, a shared Resolver rule. These are kept with the owner's account id and marked as owned elsewhere;
  • the CIDR of a peered VPC, when the peering answer includes it;
  • the account name, if you give it one (below).

It cannot read:

  • anything inside the other account: its VPCs, route tables, subnets or security groups. The tool never calls AWS with credentials it does not have. What you see of that side is a stub: a box that says "this exists", drawn dashed.
  • the other account's name, unless the scanned account can read the organization (organizations:ListAccounts works only from the management account) or you supply it.

A stub is a resource that was referenced but never described. The count of resources in the inventory leaves stubs out, and the graph marks them stub: true. Treat a stub as a pointer, not as verified data.

Naming accounts

Give readable labels with --account-names, a JSON file that maps account ids to labels:

{ "222222222222": "network-hub", "333333333333": "partner-analytics" }
vpcatlas discover --profile spoke --account-names ./accounts.json --output-dir ./out

A label from the file wins over a name read from the organization. The same file is read by the dashboard through vpcatlas serve --account-names or VPCATLAS_ACCOUNT_NAMES_FILE.

Scanning several accounts in one run

If you have credentials for both sides, scan both. The accounts you scan stop being external, the stubs that were created while scanning a spoke are replaced by the real resources found in the hub, and the links become edges between two real accounts.

Repeat --profile, one profile per account:

vpcatlas discover --profile prod --profile staging --profile network-hub --output-dir ./out

Or repeat --role-arn, and assume a role in each account from the identity you already have:

vpcatlas discover \
  --role-arn arn:aws:iam::111122223333:role/AtlasAuditor \
  --role-arn arn:aws:iam::444455556666:role/AtlasAuditor \
  --external-id <external-id> --output-dir ./out

Rules:

  • Use several --profile or several --role-arn, not both. One --profile together with several --role-arn is fine: the profile is the identity that assumes each role.
  • A comma works as well as repetition: --role-arn arn:...:role/A,arn:...:role/B.
  • The ExternalId, the MFA options, the session duration and the session name apply to every role of the run. If your roles need different ExternalIds, give them the same one or scan them in separate runs; each run writes its own graph.
  • --regions and --exclude-regions apply to every account. The accounts are scanned one after the other, and the regions of each in parallel, so the time is the sum of the scans.
  • If one account cannot be reached (the role cannot be assumed, the credentials are expired), the run stops with exit code 3 and no files are written, not even for the accounts that were scanned before it. Fix that account or drop it from the command.
  • The dashboard does the same with VPCATLAS_PROFILE and VPCATLAS_ROLE_ARN, comma separated; each refresh scans every account again.

How to create the role in each account, with a trust policy and an ExternalId, is in IAM.

The merged graph and inventory list every audited account, and the summary counts them as "Audited accounts".

Organizations: not available yet

Scanning every account of an AWS Organization from a list is planned for v2 and is not in 0.1.0. Today you list the accounts yourself, with repeated --profile or --role-arn. If the scanned account is the management account, the tool does read organizations:ListAccounts to put names on the accounts it meets; that gives names, not discovery.

A common way to get the list is to deploy the same auditor role to every account with CloudFormation StackSets or Terraform, then pass the role ARNs:

aws organizations list-accounts --query 'Accounts[?Status==`ACTIVE`].Id' --output text \
  | tr '\t' '\n' | sed 's|^|--role-arn arn:aws:iam::|; s|$|:role/AtlasAuditor|'

The output is a list of --role-arn options you can paste into the command. Check it against the accounts you mean to scan before you run it.