Full inventory scan of an AWS account — discovers every resource across all regions and services, outputs a Keysight-branded HTML report and multi-sheet XLSX workbook. Reports include a live API call count showing exactly how many AWS API calls were made to produce the scan.
This is the Services-integrated version of the scanner. It lives under AWS/Services/Account/Scanner/ and shares auth + S3 upload infrastructure with the rest of the Services/ family.
AWS/Services/Account/Scanner/
├── account_scanner.py # Entry point — CLI, orchestration, multi-account loop
├── config.json # Auth, scan scope, output settings
├── requirements.txt
└── modules/
├── auth.py # Session helpers (SSO, IAM, region discovery, org accounts)
├── service_scanners.py # All dedicated service scanners + SCANNERS registry
├── ce_discovery.py # Cost Explorer–driven service/region discovery
├── generic_scanner.py # Fallback scanner for services with no dedicated scanner
└── report_generator.py # HTML + XLSX report generation (Keysight branded)
- Auto-discovers all enabled regions for the account
- CE-driven discovery (default): queries Cost Explorer to find only active services and their regions — no wasted API calls on empty services
- Scans 24+ services in parallel: EC2, EBS, EIP, AMI, RDS, S3, Lambda, EKS, ECS, ELB/ALB/NLB, VPC, IAM, CloudFormation, DynamoDB, SQS, SNS, ElastiCache, CloudWatch, Route53, ACM, Secrets Manager, KMS, CloudTrail, CloudFront
- Generic scanner fallback: CE-discovered services with no dedicated scanner are still inventoried (ARN + tags)
- Optional deep scans per service (OS names, S3 content classification, ALB target health, CloudWatch log group sizes)
- Optional Cost Explorer analysis (last N days, by service + region, plus usage-type breakdown)
- Optional EOL report — at-risk OS instances only, accounts with no risk are silently skipped
- Outputs: HTML (searchable, per-service sections, API call count KPI) + XLSX (Summary sheet + one sheet per service)
- Auto-uploads reports + logs to S3 (
YOUR_S3_BUCKET/services/account/scanner/)
cd AWS/Services/Account/Scanner
pip install -r requirements.txtStep 1 — check your auth.mode in config.json
"auth": {
"mode": "sso" ← this controls everything
}auth.mode |
Pre-flight required |
|---|---|
sso |
Must run aws sso login --sso-session YOUR_SSO_SESSION before every session |
iam_mfa_switch_role |
No pre-flight — script prompts for MFA token at runtime |
iam_direct |
No pre-flight — credentials are in config.json |
Step 2 — if using SSO, login first (mandatory, session expires)
aws sso login --sso-session YOUR_SSO_SESSIONStep 3 — then run the script
python account_scanner.pySet auth.mode in config.json. Three modes are supported:
Best for: teams using AWS SSO. Supports scanning one account, a list, or every account in the org.
"auth": {
"mode": "sso",
"sso": {
"profile": "YOUR_SSO_PROFILE",
"session_name": "AccountScanSession"
}
},
"organization": {
"management_account_id": "YOUR_MANAGEMENT_ACCOUNT_ID",
"role_name": "YOUR_AUTOMATION_ROLE_NAME",
"external_id": "YOUR_EXTERNAL_ID",
"mode": "specific",
"accounts": ["YOUR_ACCOUNT_ID"],
"exclude_accounts": ["YOUR_MANAGEMENT_ACCOUNT_ID"]
}Prerequisites:
aws sso login --sso-session YOUR_SSO_SESSIONFlow: YOUR_SSO_PROFILE SSO hub → sts:AssumeRole (YOUR_AUTOMATION_ROLE_NAME) per target account
Multi-account options (SSO only):
# Scan all accounts in the org
python account_scanner.py --all-accounts
# Scan specific accounts (overrides config)
python account_scanner.py --accounts 123456789012 987654321098
# Scan accounts listed in config.json organization.accounts (default)
python account_scanner.pyEach account gets its own report file. Rows include Account ID and Account Name columns when scanning multiple accounts.
Best for: service accounts or roles that don't require MFA.
"auth": {
"mode": "iam_direct",
"iam_direct": {
"access_key_id": "YOUR_ACCESS_KEY_ID",
"secret_access_key": "YOUR_SECRET_ACCESS_KEY",
"region": "us-east-1"
}
}Best for: human IAM users with MFA enabled who need to switch into another account.
"auth": {
"mode": "iam_mfa_switch_role",
"iam_mfa_switch_role": {
"access_key_id": "YOUR_ACCESS_KEY_ID",
"secret_access_key": "YOUR_SECRET_ACCESS_KEY",
"mfa_serial": "arn:aws:iam::SOURCE_ACCOUNT_ID:mfa/MFA_DEVICE_NAME",
"mfa_token_duration": 3600,
"target_role_arn": "arn:aws:iam::TARGET_ACCOUNT_ID:role/ROLE_NAME",
"session_name": "AccountScanSession"
}
}At runtime the script prompts:
Enter MFA token for [arn:aws:iam::YOUR_ACCOUNT_ID:mfa/YOUR_MFA_DEVICE]: 123456
Flow: IAM keys → sts:GetSessionToken (with MFA) → sts:AssumeRole (into target account)
python account_scanner.pyQueries Cost Explorer first to find active services and their regions, then scans only those. Output: account_scan_<id>_full_<ts>.html/xlsx
python account_scanner.py --services ec2 rds s3
python account_scanner.py --services lambda dynamodbValid values: ec2 ebs eip ami rds s3 lambda eks ecs elb vpc iam cloudformation dynamodb sqs sns elasticache cloudwatch route53 acm secretsmanager kms cloudtrail cloudfront
python account_scanner.py --regions us-east-1 us-east-2 us-west-2
python account_scanner.py --services ec2 rds --regions us-east-1 eu-west-1python account_scanner.py --deep s3 # content classification, size, encryption
python account_scanner.py --deep ec2 # resolved OS name, EOL columns, SG rules
python account_scanner.py --deep alb # listeners, target health, WAF, certs
python account_scanner.py --deep cloudwatch # log groups, stored size, retention, last ingestion
python account_scanner.py --deep all # all four deep scans
python account_scanner.py --services ec2 rds --deep s3 # standard ec2+rds + deep s3What each deep scan adds:
--deep |
Extra columns vs standard scan |
|---|---|
ec2 |
OS name (resolved from AMI), OS Family, EOL Status, EOL Date, Days Until EOL, security group rules, key pair, IAM profile, CPU detail, attached EBS |
s3 |
Content Classification, Top Prefixes (sampled), Last Object Written, Last Written Age, Last Object Key, Est. Total Size, Est. Objects, Encryption, Lifecycle Rules, Replication, Website Hosting, Access Logging |
alb |
Listeners (port/protocol/SSL policy), Target Groups (healthy/unhealthy/unused counts), WAF WebACL, Access Logs bucket, Deletion Protection, Idle Timeout, Certificates |
cloudwatch |
Stored Size, Retention Policy, Last Ingestion, Last Ingestion Age |
Finds EC2 instances running outdated or soon-expiring operating systems. Uses a built-in database of OS versions (no external API calls beyond EC2). Accounts with zero at-risk instances are silently skipped.
python account_scanner.py --eol # all OS families
python account_scanner.py --eol --eol-os windows # Windows only
python account_scanner.py --eol --eol-os windows ubuntu rhel
python account_scanner.py --eol --all-accounts # entire org
python account_scanner.py --deep ec2 --eol --accounts YOUR_ACCOUNT_IDRisk levels:
| Risk | Meaning |
|---|---|
CRITICAL |
Past EOL — no extended support available |
HIGH |
Past EOL — extended support exists (costs extra) |
MEDIUM |
Expiring within 90 days |
LOW |
Expiring within 1 year |
--eol-os families: windows ubuntu amazon rhel / redhat centos debian suse
Generates two sections: service-level totals (by service + region) and usage-type breakdown (e.g. USE2-TimedStorage-ByteHrs = CloudWatch logs storage in us-east-2).
python account_scanner.py --cost # last 30 days
python account_scanner.py --cost --cost-days 90 # last 90 days
python account_scanner.py --deep cloudwatch --costaws sso login --sso-session YOUR_SSO_SESSION
python account_scanner.py --all-accounts
python account_scanner.py --accounts YOUR_ACCOUNT_ID YOUR_ACCOUNT_ID
python account_scanner.py --eol --eol-os windows --all-accountsEach account produces its own report. Account ID and Account Name columns are added to every row.
Only include resources that match all specified tags. Works for: EC2, EBS, EIP, AMI, VPC/Subnet/IGW/NAT, Lambda, EKS.
python account_scanner.py --tag-filter Environment=prod
python account_scanner.py --tag-filter Owner=network-team
python account_scanner.py --tag-filter Environment=prod Owner=infra-team # AND
python account_scanner.py --tag-filter CostCenter # key-only match
python account_scanner.py --deep ec2 --tag-filter Environment=prod --all-accountsOutput filename gets a _tagged suffix.
Note: Services without tag data in scan results (RDS, ECS, CloudFormation) return no rows when
--tag-filteris active. Use--servicesto limit to tag-capable services.
python account_scanner.py --no-html # XLSX only
python account_scanner.py --no-xlsx # HTML only
python account_scanner.py --output-dir /tmp/reports # change output directory
python account_scanner.py --config prod_config.json # alternate config file# Full inventory + cost — standard weekly run
python account_scanner.py --cost
# Investigate a surprise CloudWatch bill
python account_scanner.py --deep cloudwatch --cost --regions us-east-2
# S3 audit — size, content, last write
python account_scanner.py --deep s3
# Org-wide EOL sweep — Windows + RHEL (most common risk)
python account_scanner.py --eol --eol-os windows rhel --all-accounts
# Full deep scan + cost for a specific account
python account_scanner.py --deep all --cost --accounts 111122223333
# Quick targeted check
python account_scanner.py --services ec2 rds --regions us-east-1 eu-west-1
# Deep EC2 scoped to a team's instances
python account_scanner.py --deep ec2 --tag-filter Owner=network-team --all-accountsReports are saved to output/ with descriptive names:
| Command | Output filename |
|---|---|
| (full scan) | account_scan_123456789012_full_20260312_143022.html |
--deep s3 |
account_scan_123456789012_s3-deep_20260312_143022.html |
--deep all |
account_scan_123456789012_ec2-deep_s3-deep_alb-deep_cloudwatch-deep_20260312.html |
--cost |
account_scan_123456789012_cost_20260312_143022.html |
--eol |
account_scan_123456789012_eol_20260312_143022.html |
--services ec2 rds |
account_scan_123456789012_ec2_rds_20260312_143022.html |
Both .html and .xlsx are generated with the same base name. A .log file is also written alongside. Reports and logs are automatically uploaded to S3.
Every report includes a live API call count — the total number of AWS API calls made during the scan. This appears as:
- A KPI tile in the HTML report header
- A
API Calls Maderow in the XLSX Summary sheet - A line in the console summary table
Typical call counts for a full scan across 20 regions with 24 services:
- ~200–500 calls (CE-driven, sparse account)
- ~1,000–3,000 calls (full explicit scan, dense account)
- ~5,000–10,000 calls (
--deep allacross a large account)
The count covers every AWS SDK call: describe_*, list_*, get_*, paginator pages, and auth calls (STS, IAM).
By default (no --services flag), the scanner queries Cost Explorer once to find which services have been billed in the last 30 days, then scans only those services in their active regions. This avoids making hundreds of describe_* calls to services that aren't in use.
- If CE is unavailable (no
ce:GetCostAndUsagepermission), falls back to scanning all known services - CE billing names without a dedicated scanner use the generic scanner (ARN + tags via
botocore) - IAM is always included regardless of CE (it's free-tier, never billed)
config.jsonandoutput/are gitignored — credentials never leave your machine- S3 size/object counts come from CloudWatch metrics (1–2 day lag; new buckets show
n/a) - Cost Explorer requires
ce:GetCostAndUsagepermission on the target role - IAM scan (users, roles, groups) is global and runs once regardless of region count
- Reports upload to
s3://YOUR_S3_BUCKET/services/account/scanner/{logs|reports}/YYYY/MM/DD/