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
What Gets Discovered
| Service | Resource Types |
|---|---|
| EC2 | Instances, AMIs, Security Groups, Key Pairs, Elastic IPs, Snapshots, Volumes, Launch Templates |
| VPC | VPCs, Subnets, Route Tables, Internet Gateways, NAT Gateways, VPC Peerings, Transit Gateways, VPC Endpoints |
| S3 | Buckets, Bucket policies, ACLs, encryption config, public access settings |
| RDS | DB instances, DB clusters (Aurora), Parameter groups, Subnet groups, Snapshots |
| IAM | Users, Roles, Groups, Policies, Access keys (metadata), MFA devices |
| Lambda | Functions, Aliases, Event source mappings, Resource-based policies |
| EKS | Clusters, Node groups, Add-ons, OIDC providers |
| ECS | Clusters, Services, Task definitions, Tasks |
| ELB | Load Balancers (ALB/NLB/CLB), Target groups, Listeners |
| Route 53 | Hosted zones, DNS records, Health checks |
| CloudFront | Distributions, Origins, Cache behaviors |
| KMS | Keys, Key policies, Key aliases |
| Secrets Manager | Secrets (metadata only, no values) |
| CloudTrail | Trails, event selectors |
| GuardDuty | Detectors, findings (high/critical) |
| Security Hub | Findings, standards, controls |
| Organizations | OUs, 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
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
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
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:
- 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.