Skip to main content

AWS Discovery

The Infracast AWS plugin uses the AWS SDK to enumerate resources across your accounts and regions. It authenticates via IAM role assumption, which means no long-lived access keys are required — only a trust relationship between your AWS account and Infracast's collector.

How It Works

  1. Infracast assumes a discovery role in your AWS account via sts:AssumeRole
  2. The plugin enumerates resources across specified regions using read-only API calls
  3. Discovered resources are normalized and written to the Infracast graph
  4. On subsequent runs, only changed resources are updated (incremental mode)

IAM Role Setup

Step 1: Create the Discovery Role

In each AWS account you want to discover, create an IAM role named InfracastDiscovery (or your preferred name).

Option A: AWS CLI

# Create the role with a trust policy
aws iam create-role \
--role-name InfracastDiscovery \
--assume-role-policy-document file://trust-policy.json \
--description "Role for Infracast infrastructure discovery"

Option B: AWS Console

  1. Navigate to IAM → Roles → Create role
  2. Select Another AWS account as the trusted entity
  3. Enter Infracast's AWS account ID (provided in your Infracast settings under Settings → Discovery → AWS Account ID)
  4. Enable Require external ID and enter your organization's external ID

Step 2: Trust Policy

The trust policy controls who can assume this role. Replace INFRACAST_ACCOUNT_ID with the account ID shown in your Infracast console.

trust-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InfracastAssumeRole",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::INFRACAST_ACCOUNT_ID:role/infracast-collector"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "your-unique-external-id"
}
}
}
]
}
tip

Use a strong, unique external ID (UUID format recommended). This prevents confused deputy attacks where a third party tricks Infracast into assuming your role. Store it in your secrets manager and record it in Infracast's credential configuration.

Step 3: Attach the Discovery Policy

Attach the following managed policy to the InfracastDiscovery role. This policy grants read-only access to all supported services.

infracast-discovery-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "InfracastEC2Discovery",
"Effect": "Allow",
"Action": [
"ec2:Describe*",
"ec2:GetManagedPrefixListEntries",
"ec2:GetTransitGatewayAttachmentPropagations",
"ec2:GetTransitGatewayRouteTableAssociations",
"ec2:GetTransitGatewayRouteTablePropagations",
"ec2:SearchLocalGatewayRoutes",
"ec2:SearchTransitGatewayRoutes"
],
"Resource": "*"
},
{
"Sid": "InfracastVPCDiscovery",
"Effect": "Allow",
"Action": [
"network-firewall:Describe*",
"network-firewall:List*"
],
"Resource": "*"
},
{
"Sid": "InfracastS3Discovery",
"Effect": "Allow",
"Action": [
"s3:GetBucketAcl",
"s3:GetBucketCORS",
"s3:GetBucketEncryption",
"s3:GetBucketLifecycle",
"s3:GetBucketLocation",
"s3:GetBucketLogging",
"s3:GetBucketNotification",
"s3:GetBucketObjectLockConfiguration",
"s3:GetBucketPolicy",
"s3:GetBucketPolicyStatus",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketReplication",
"s3:GetBucketRequestPayment",
"s3:GetBucketTagging",
"s3:GetBucketVersioning",
"s3:GetBucketWebsite",
"s3:GetEncryptionConfiguration",
"s3:ListAllMyBuckets",
"s3:ListBucket"
],
"Resource": "*"
},
{
"Sid": "InfracastRDSDiscovery",
"Effect": "Allow",
"Action": [
"rds:Describe*",
"rds:ListTagsForResource"
],
"Resource": "*"
},
{
"Sid": "InfracastIAMDiscovery",
"Effect": "Allow",
"Action": [
"iam:GenerateCredentialReport",
"iam:GetAccountAuthorizationDetails",
"iam:GetAccountPasswordPolicy",
"iam:GetAccountSummary",
"iam:GetCredentialReport",
"iam:GetGroupPolicy",
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:GetRolePolicy",
"iam:GetUserPolicy",
"iam:List*",
"iam:SimulateCustomPolicy",
"iam:SimulatePrincipalPolicy"
],
"Resource": "*"
},
{
"Sid": "InfracastLambdaDiscovery",
"Effect": "Allow",
"Action": [
"lambda:GetAccountSettings",
"lambda:GetFunction",
"lambda:GetFunctionConfiguration",
"lambda:GetPolicy",
"lambda:ListAliases",
"lambda:ListEventSourceMappings",
"lambda:ListFunctions",
"lambda:ListTags",
"lambda:ListVersionsByFunction"
],
"Resource": "*"
},
{
"Sid": "InfracastEKSDiscovery",
"Effect": "Allow",
"Action": [
"eks:Describe*",
"eks:List*"
],
"Resource": "*"
},
{
"Sid": "InfracastECSDiscovery",
"Effect": "Allow",
"Action": [
"ecs:Describe*",
"ecs:List*"
],
"Resource": "*"
},
{
"Sid": "InfracastCloudTrailDiscovery",
"Effect": "Allow",
"Action": [
"cloudtrail:DescribeTrails",
"cloudtrail:GetEventSelectors",
"cloudtrail:GetInsightSelectors",
"cloudtrail:GetTrail",
"cloudtrail:GetTrailStatus",
"cloudtrail:ListTags",
"cloudtrail:ListTrails",
"cloudtrail:LookupEvents"
],
"Resource": "*"
},
{
"Sid": "InfracastConfigDiscovery",
"Effect": "Allow",
"Action": [
"config:Describe*",
"config:Get*",
"config:List*"
],
"Resource": "*"
},
{
"Sid": "InfracastSecurityHubDiscovery",
"Effect": "Allow",
"Action": [
"securityhub:Describe*",
"securityhub:Get*",
"securityhub:List*"
],
"Resource": "*"
},
{
"Sid": "InfracastOrganizationsDiscovery",
"Effect": "Allow",
"Action": [
"organizations:Describe*",
"organizations:List*"
],
"Resource": "*"
},
{
"Sid": "InfracastRoute53Discovery",
"Effect": "Allow",
"Action": [
"route53:Get*",
"route53:List*",
"route53domains:Get*",
"route53domains:List*"
],
"Resource": "*"
},
{
"Sid": "InfracastKMSDiscovery",
"Effect": "Allow",
"Action": [
"kms:Describe*",
"kms:Get*",
"kms:List*"
],
"Resource": "*"
},
{
"Sid": "InfracastSecretsDiscovery",
"Effect": "Allow",
"Action": [
"secretsmanager:Describe*",
"secretsmanager:List*"
],
"Resource": "*"
},
{
"Sid": "InfracastSNSSQSDiscovery",
"Effect": "Allow",
"Action": [
"sns:Get*",
"sns:List*",
"sqs:Get*",
"sqs:List*"
],
"Resource": "*"
},
{
"Sid": "InfracastElasticSearchDiscovery",
"Effect": "Allow",
"Action": [
"es:Describe*",
"es:List*",
"opensearch:Describe*",
"opensearch:List*"
],
"Resource": "*"
},
{
"Sid": "InfracastCloudFrontDiscovery",
"Effect": "Allow",
"Action": [
"cloudfront:Describe*",
"cloudfront:Get*",
"cloudfront:List*"
],
"Resource": "*"
},
{
"Sid": "InfracastELBDiscovery",
"Effect": "Allow",
"Action": [
"elasticloadbalancing:Describe*"
],
"Resource": "*"
},
{
"Sid": "InfracastGuardDutyDiscovery",
"Effect": "Allow",
"Action": [
"guardduty:Get*",
"guardduty:List*"
],
"Resource": "*"
}
]
}

Apply the policy:

# Create the policy
aws iam create-policy \
--policy-name InfracastDiscoveryPolicy \
--policy-document file://infracast-discovery-policy.json

# Attach to the role
aws iam attach-role-policy \
--role-name InfracastDiscovery \
--policy-arn arn:aws:iam::123456789012:policy/InfracastDiscoveryPolicy

Registering the Credential in Infracast

infracast creds add \
--plugin aws \
--name "prod-account-123456789012" \
--type assume-role \
--role-arn "arn:aws:iam::123456789012:role/InfracastDiscovery" \
--external-id "your-unique-external-id"

Configuring the Discovery Job

infracast.yaml
discovery:
jobs:
- name: aws-prod
plugin: aws
credential: prod-account-123456789012
schedule: "*/15 * * * *"
config:
account_id: "123456789012"
regions:
- us-east-1
- us-west-2
- eu-west-1
# Optional: skip specific services
exclude_services:
- guardduty # if GuardDuty not enabled
# Optional: tag filters (only discover tagged resources)
tag_filters:
Environment: production

Troubleshooting

AccessDenied when assuming role

Symptom: Error: AccessDenied: User: arn:aws:sts::... is not authorized to perform: sts:AssumeRole

Checks:

  1. Verify the Infracast collector account ID in the trust policy matches the account ID in Settings → Discovery → AWS Account ID
  2. Verify the external ID in the trust policy exactly matches what's in the Infracast credential
  3. Check there's no SCP blocking sts:AssumeRole from the target account
# Test the assume-role manually from Infracast's account
aws sts assume-role \
--role-arn "arn:aws:iam::TARGET_ACCOUNT:role/InfracastDiscovery" \
--role-session-name test \
--external-id your-external-id

Missing resources in specific regions

Symptom: Resources exist in AWS console but don't appear in Infracast

Checks:

  1. Verify the region is listed in regions: in your job config
  2. Some global services (IAM, S3, Route 53, CloudFront) are always discovered regardless of region config
  3. Check if the region requires opt-in: aws ec2 describe-regions --all-regions

Rate limiting / throttling errors

Symptom: Error: RequestLimitExceeded or ThrottlingException in job logs

Solution: Enable incremental mode and reduce job frequency:

config:
incremental: true
api_rate_limit_rps: 10 # requests per second (default: 20)
regions:
- us-east-1 # start with one region to test

IAM credential report fails

Symptom: Error: LimitExceeded: Only one report at a time

Solution: This is normal if multiple jobs run simultaneously. Infracast serializes credential report generation automatically. If the issue persists, check that only one Infracast job is configured per account.

CloudFormation StackSet deployment fails

Symptom: Stack instances fail with InsufficientCapabilitiesException

Solution: Ensure you passed --capabilities CAPABILITY_NAMED_IAM to the create-stack-set command.

Multi-Account / AWS Organizations Discovery

Starting with Build 109, Infracast can auto-discover every account in your AWS Organization without manual per-account configuration.

How It Works

Infracast assumes a management account role that has organizations:ListAccounts permission, then fans out to assume the InfracastDiscovery role in each active member account concurrently (up to 10 parallel). New accounts added to the org are automatically picked up on the next scan run.

Setup

  1. Navigate to Settings → Connectors → AWS → Add
  2. Toggle Organization Mode on
  3. From your management account's CloudShell, run the one-command StackSets script provided in the UI to deploy InfracastDiscovery to all accounts
  4. Paste the Management Role ARN and External ID
infracast.yaml (organizations mode)
discovery:
jobs:
- name: aws-org-discovery
plugin: aws
credential: org-management-role
schedule: "0 */6 * * *"
config:
mode: organizations
management_account_id: "123456789012"
management_role_arn: "arn:aws:iam::123456789012:role/InfracastOrgDiscovery"
member_role_name: InfracastDiscovery
external_id: your-external-id
regions:
- us-east-1
- us-west-2
# Optional: exclude specific accounts or OUs
exclude_accounts:
- "111111111111" # sandbox account
info

With Organizations mode, Infracast automatically discovers new accounts added to the org on subsequent runs. No manual configuration needed per account.


Resource Relationships

Starting with Build 109.5 and Build 112, Infracast wires containment and security relationship edges between discovered resources so the topology graph reflects real infrastructure dependencies.

Containment Edges

All 21 AWS service scanner functions emit contains edges to parent nodes (VPC, subnet, account). Resources no longer appear as floating disconnected nodes in the topology.

Security Relationship Edges (Build 112)

The following directed edges are emitted to capture IAM chains, encryption relationships, and data-flow paths:

FromToEdge TypeDescription
aws.iam.policyaws.iam.roleattached_toPolicy is attached to a role
aws.iam.policyaws.iam.userattached_toPolicy is attached to a user
aws.lambda.functionaws.iam.roleexecutes_asLambda uses this role for execution
aws.ec2.instanceaws.iam.roledepends_onEC2 uses instance profile role
aws.cloudtrail.trailaws.s3.bucketlogs_toCloudTrail ships logs to this S3 bucket
aws.config.recorderaws.s3.bucketlogs_toConfig recorder delivers to this S3 bucket
aws.cloudfront.distributionaws.s3.bucketdepends_onCloudFront serves from this S3 origin
aws.rds.instance / aws.lambda.function / aws.ebs.volume / aws.secretsmanager.secret / aws.efs.filesystem / aws.sqs.queueaws.kms.keyencrypted_byResource is encrypted by this KMS key

These edges power:

  • IAM blast radius analysis — trace policy → role → compute resource chains
  • Encryption coverage checks — identify unencrypted resources at a glance
  • Audit log integrity — verify CloudTrail and Config logs flow to expected S3 buckets
  • Attack path chaining — see Attack Path Analysis for how these edges enable end-to-end attack chain tracing

Resource Type Reference

The following AWS resource types are discovered and stored in the topology graph:

Compute

TypeDescription
aws.ec2.instanceEC2 virtual machine
aws.ec2.amiAmazon Machine Image
aws.ec2.launch_templateEC2 launch template
aws.ec2.auto_scaling_groupAuto Scaling group
aws.lambda.functionLambda serverless function
aws.ecs.clusterECS cluster
aws.ecs.serviceECS service
aws.ecs.task_definitionECS task definition
aws.eks.clusterEKS Kubernetes cluster
aws.ecr.repositoryECR container image repository

Networking

TypeDescription
aws.ec2.vpcVirtual Private Cloud
aws.ec2.subnetVPC subnet
aws.ec2.security_groupSecurity group
aws.ec2.network_aclNetwork ACL (NACL)
aws.ec2.vpc_peering_connectionVPC peering connection
aws.ec2.route_tableRoute table
aws.ec2.internet_gatewayInternet gateway
aws.ec2.nat_gatewayNAT gateway
aws.ec2.transit_gatewayTransit Gateway
aws.ec2.vpn_gatewayVPN gateway
aws.ec2.direct_connect_gatewayDirect Connect gateway
aws.directconnect.connectionDirect Connect connection
aws.ec2.network_interfaceElastic network interface
aws.ec2.elastic_ipElastic IP address
aws.elb.load_balancerClassic/ALB/NLB load balancer
aws.wafv2.web_aclWAF v2 Web ACL
aws.apigateway.rest_apiAPI Gateway REST API
aws.apigateway.http_apiAPI Gateway HTTP API

Storage & Databases

TypeDescription
aws.s3.bucketS3 bucket
aws.ebs.volumeEBS block volume
aws.ebs.snapshotEBS snapshot
aws.efs.filesystemElastic File System
aws.rds.instanceRDS database instance
aws.rds.clusterRDS Aurora cluster
aws.dynamodb.tableDynamoDB table
aws.elasticache.clusterElastiCache cluster (Redis/Memcached)
aws.elasticache.replication_groupElastiCache replication group
aws.redshift.clusterRedshift data warehouse cluster
aws.opensearch.domainOpenSearch (Elasticsearch) domain
aws.msk.clusterMSK (Managed Kafka) cluster

Identity & Access

TypeDescription
aws.iam.roleIAM role
aws.iam.userIAM user
aws.iam.policyIAM managed policy
aws.iam.groupIAM group
aws.cognito.user_poolCognito user pool
aws.cognito.identity_poolCognito identity pool

DNS, CDN & Certificates

TypeDescription
aws.route53.hosted_zoneRoute 53 hosted zone
aws.route53.recordRoute 53 DNS record
aws.cloudfront.distributionCloudFront distribution
aws.acm.certificateACM TLS/SSL certificate

Security & Compliance

TypeDescription
aws.kms.keyKMS customer-managed key
aws.secretsmanager.secretSecrets Manager secret
aws.cloudtrail.trailCloudTrail audit trail
aws.guardduty.detectorGuardDuty detector
aws.securityhub.hubSecurity Hub hub
aws.config.recorderAWS Config recorder
aws.inspector.assessment_templateInspector assessment template

Messaging & Integration

TypeDescription
aws.sns.topicSNS topic
aws.sqs.queueSQS queue

Infrastructure as Code

TypeDescription
aws.cloudformation.stackCloudFormation stack
aws.cloudformation.stack_setCloudFormation stack set

Route Table Discovery (Build 90)

Starting with Build 90, the AWS discovery plugin collects full routing data from each route table, not just the route table node inventory.

What's Collected

For each route table, the plugin now emits:

routes_to edges — one per route entry, with properties:

  • destination_cidr — the destination CIDR block (e.g. 0.0.0.0/0, 10.0.0.0/8)
  • target_type — type of the target (ec2.internet_gateway, ec2.nat_gateway, ec2.transit_gateway, etc.)
  • state — route state (active, blackhole)
  • propagated — whether this is a propagated route (from a VGW/TGW)
  • blackholetrue if the target no longer exists

associated_with edges — from each explicitly-associated subnet to its route table.

Node properties added to route table nodes:

  • IsMainRouteTable — whether this is the VPC's main (default) route table
  • DefaultRouteTarget — node ID of the target for the 0.0.0.0/0 route, if any

Supported Target Types

AWS TargetNode Type
Internet Gatewayec2.internet_gateway
NAT Gatewayec2.nat_gateway
Transit Gatewayec2.transit_gateway
VPN Gatewayec2.vpn_gateway
VPC Peering Connectionec2.vpc_peering_connection
Network Interfaceec2.network_interface
Egress-Only Internet Gatewayec2.egress_only_igw

Local routes (to the VPC CIDR itself) are skipped — they're implicit and would create noise.