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

What Gets Discovered

ServiceResource Types
EC2Instances, AMIs, Security Groups, Key Pairs, Elastic IPs, Snapshots, Volumes, Launch Templates
VPCVPCs, Subnets, Route Tables, Internet Gateways, NAT Gateways, VPC Peerings, Transit Gateways, VPC Endpoints
S3Buckets, Bucket policies, ACLs, encryption config, public access settings
RDSDB instances, DB clusters (Aurora), Parameter groups, Subnet groups, Snapshots
IAMUsers, Roles, Groups, Policies, Access keys (metadata), MFA devices
LambdaFunctions, Aliases, Event source mappings, Resource-based policies
EKSClusters, Node groups, Add-ons, OIDC providers
ECSClusters, Services, Task definitions, Tasks
ELBLoad Balancers (ALB/NLB/CLB), Target groups, Listeners
Route 53Hosted zones, DNS records, Health checks
CloudFrontDistributions, Origins, Cache behaviors
KMSKeys, Key policies, Key aliases
Secrets ManagerSecrets (metadata only, no values)
CloudTrailTrails, event selectors
GuardDutyDetectors, findings (high/critical)
Security HubFindings, standards, controls
OrganizationsOUs, accounts, SCPs

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

Multi-Account Setup with AWS Organizations

For organizations with many accounts, Infracast can discover all accounts automatically using an Organizations management role.

Architecture

┌─────────────────────────────────────┐
│ Management Account (root) │
│ │
│ InfracastOrgDiscovery role │
│ (lists accounts via Organizations) │
└──────────────┬──────────────────────┘
│ STS AssumeRole
┌──────────┼──────────┐
▼ ▼ ▼
Account A Account B Account C
(InfracastDiscovery role in each)

Step 1: Deploy the Discovery Role to All Accounts

Use CloudFormation StackSets to deploy the role to all accounts in one operation:

# Deploy to all accounts in the organization
aws cloudformation create-stack-set \
--stack-set-name InfracastDiscovery \
--template-body file://infracast-role-stackset.yaml \
--parameters \
ParameterKey=InfracastAccountId,ParameterValue=INFRACAST_ACCOUNT_ID \
ParameterKey=ExternalId,ParameterValue=your-external-id \
--capabilities CAPABILITY_NAMED_IAM \
--permission-model SERVICE_MANAGED \
--auto-deployment Enabled=true,RetainStacksOnAccountRemoval=false

# Deploy stack instances to all OUs
aws cloudformation create-stack-instances \
--stack-set-name InfracastDiscovery \
--deployment-targets OrganizationalUnitIds=r-xxxx \
--regions us-east-1

Step 2: Configure Multi-Account Job

infracast.yaml
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 # role name in each member account
external_id: your-external-id
regions:
- us-east-1
- us-west-2
# Optional: exclude specific OUs or accounts
exclude_accounts:
- "111111111111" # sandbox account
exclude_ous:
- ou-xxxx-sandbox
info

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

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.