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
- Infracast assumes a discovery role in your AWS account via
sts:AssumeRole - The plugin enumerates resources across specified regions using read-only API calls
- Discovered resources are normalized and written to the Infracast graph
- 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
- Navigate to IAM → Roles → Create role
- Select Another AWS account as the trusted entity
- Enter Infracast's AWS account ID (provided in your Infracast settings under Settings → Discovery → AWS Account ID)
- 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.
{
"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"
}
}
}
]
}
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.
{
"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
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:
- Verify the Infracast collector account ID in the trust policy matches the account ID in Settings → Discovery → AWS Account ID
- Verify the external ID in the trust policy exactly matches what's in the Infracast credential
- Check there's no SCP blocking
sts:AssumeRolefrom 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:
- Verify the region is listed in
regions:in your job config - Some global services (IAM, S3, Route 53, CloudFront) are always discovered regardless of region config
- 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
- Navigate to Settings → Connectors → AWS → Add
- Toggle Organization Mode on
- From your management account's CloudShell, run the one-command StackSets script provided in the UI to deploy
InfracastDiscoveryto all accounts - Paste the Management Role ARN and External ID
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
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:
| From | To | Edge Type | Description |
|---|---|---|---|
aws.iam.policy | aws.iam.role | attached_to | Policy is attached to a role |
aws.iam.policy | aws.iam.user | attached_to | Policy is attached to a user |
aws.lambda.function | aws.iam.role | executes_as | Lambda uses this role for execution |
aws.ec2.instance | aws.iam.role | depends_on | EC2 uses instance profile role |
aws.cloudtrail.trail | aws.s3.bucket | logs_to | CloudTrail ships logs to this S3 bucket |
aws.config.recorder | aws.s3.bucket | logs_to | Config recorder delivers to this S3 bucket |
aws.cloudfront.distribution | aws.s3.bucket | depends_on | CloudFront serves from this S3 origin |
aws.rds.instance / aws.lambda.function / aws.ebs.volume / aws.secretsmanager.secret / aws.efs.filesystem / aws.sqs.queue | aws.kms.key | encrypted_by | Resource 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
| Type | Description |
|---|---|
aws.ec2.instance | EC2 virtual machine |
aws.ec2.ami | Amazon Machine Image |
aws.ec2.launch_template | EC2 launch template |
aws.ec2.auto_scaling_group | Auto Scaling group |
aws.lambda.function | Lambda serverless function |
aws.ecs.cluster | ECS cluster |
aws.ecs.service | ECS service |
aws.ecs.task_definition | ECS task definition |
aws.eks.cluster | EKS Kubernetes cluster |
aws.ecr.repository | ECR container image repository |
Networking
| Type | Description |
|---|---|
aws.ec2.vpc | Virtual Private Cloud |
aws.ec2.subnet | VPC subnet |
aws.ec2.security_group | Security group |
aws.ec2.network_acl | Network ACL (NACL) |
aws.ec2.vpc_peering_connection | VPC peering connection |
aws.ec2.route_table | Route table |
aws.ec2.internet_gateway | Internet gateway |
aws.ec2.nat_gateway | NAT gateway |
aws.ec2.transit_gateway | Transit Gateway |
aws.ec2.vpn_gateway | VPN gateway |
aws.ec2.direct_connect_gateway | Direct Connect gateway |
aws.directconnect.connection | Direct Connect connection |
aws.ec2.network_interface | Elastic network interface |
aws.ec2.elastic_ip | Elastic IP address |
aws.elb.load_balancer | Classic/ALB/NLB load balancer |
aws.wafv2.web_acl | WAF v2 Web ACL |
aws.apigateway.rest_api | API Gateway REST API |
aws.apigateway.http_api | API Gateway HTTP API |
Storage & Databases
| Type | Description |
|---|---|
aws.s3.bucket | S3 bucket |
aws.ebs.volume | EBS block volume |
aws.ebs.snapshot | EBS snapshot |
aws.efs.filesystem | Elastic File System |
aws.rds.instance | RDS database instance |
aws.rds.cluster | RDS Aurora cluster |
aws.dynamodb.table | DynamoDB table |
aws.elasticache.cluster | ElastiCache cluster (Redis/Memcached) |
aws.elasticache.replication_group | ElastiCache replication group |
aws.redshift.cluster | Redshift data warehouse cluster |
aws.opensearch.domain | OpenSearch (Elasticsearch) domain |
aws.msk.cluster | MSK (Managed Kafka) cluster |
Identity & Access
| Type | Description |
|---|---|
aws.iam.role | IAM role |
aws.iam.user | IAM user |
aws.iam.policy | IAM managed policy |
aws.iam.group | IAM group |
aws.cognito.user_pool | Cognito user pool |
aws.cognito.identity_pool | Cognito identity pool |
DNS, CDN & Certificates
| Type | Description |
|---|---|
aws.route53.hosted_zone | Route 53 hosted zone |
aws.route53.record | Route 53 DNS record |
aws.cloudfront.distribution | CloudFront distribution |
aws.acm.certificate | ACM TLS/SSL certificate |
Security & Compliance
| Type | Description |
|---|---|
aws.kms.key | KMS customer-managed key |
aws.secretsmanager.secret | Secrets Manager secret |
aws.cloudtrail.trail | CloudTrail audit trail |
aws.guardduty.detector | GuardDuty detector |
aws.securityhub.hub | Security Hub hub |
aws.config.recorder | AWS Config recorder |
aws.inspector.assessment_template | Inspector assessment template |
Messaging & Integration
| Type | Description |
|---|---|
aws.sns.topic | SNS topic |
aws.sqs.queue | SQS queue |
Infrastructure as Code
| Type | Description |
|---|---|
aws.cloudformation.stack | CloudFormation stack |
aws.cloudformation.stack_set | CloudFormation 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)blackhole—trueif 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 tableDefaultRouteTarget— node ID of the target for the0.0.0.0/0route, if any
Supported Target Types
| AWS Target | Node Type |
|---|---|
| Internet Gateway | ec2.internet_gateway |
| NAT Gateway | ec2.nat_gateway |
| Transit Gateway | ec2.transit_gateway |
| VPN Gateway | ec2.vpn_gateway |
| VPC Peering Connection | ec2.vpc_peering_connection |
| Network Interface | ec2.network_interface |
| Egress-Only Internet Gateway | ec2.egress_only_igw |
Local routes (to the VPC CIDR itself) are skipped — they're implicit and would create noise.