A comprehensive serverless solution for collecting, analyzing, and optimizing AWS resource inventory across multiple accounts with advanced cost analysis and security compliance features.
This enhanced system automatically discovers and catalogs AWS resources across multiple accounts, performs cost analysis, identifies optimization opportunities, and monitors security compliance. All data is stored in a centralized DynamoDB table with Global Secondary Indexes for efficient querying.
┌─────────────────┐ ┌──────────────┐ ┌───────────────┐ ┌──────────────┐
│ EventBridge │────▶│ Lambda │────▶│ DynamoDB │────▶│ CloudWatch │
│ (Scheduled) │ │ Function │ │ Table + GSI │ │ Metrics │
└─────────────────┘ └──────┬───────┘ └───────────────┘ └──────────────┘
│ │
│ Assumes Role ▼
▼ ┌──────────────┐
┌─────────────────┐ │ SNS │
│ Target Account │ │ Topics │
│ InventoryRole │ └──────────────┘
└─────────────────┘ │
▼
┌─────────────────┐ ┌──────────────┐
│ S3 Reports │◀─────────────────────────│ Email/ │
│ Bucket │ │ Slack │
└─────────────────┘ └──────────────┘
- Multi-Account Support: Collect inventory from unlimited AWS accounts with retry logic
- Automated Collection: Multiple scheduled jobs for different purposes:
- Inventory Collection (every 12 hours)
- Cost Analysis (daily at 8 AM UTC)
- Security Checks (weekly on Mondays)
- Stale Resource Cleanup (monthly)
- Resource Types Supported:
- EC2 Instances (state, type, utilization, cost tracking)
- RDS Databases and Clusters (encryption, backup status)
- S3 Buckets (size, encryption, public access, lifecycle)
- Lambda Functions (invocations, errors, duration metrics)
- Secure Cross-Account Access: IAM role assumption with external ID
- Serverless Architecture: No infrastructure to manage
- Cost Effective: Typically < $20/month for most organizations
-
Advanced Cost Analysis:
- Real-time cost estimation using AWS pricing
- Identification of top expensive resources
- Monthly/yearly cost projections
- Department/tag-based cost allocation
- Idle resource detection with savings estimates
- Right-sizing recommendations
-
Security & Compliance:
- Automated weekly security scans
- Unencrypted resource detection
- Public access monitoring
- Compliance violation alerts via SNS
- Security dashboard metrics
-
Intelligent Querying:
- Global Secondary Indexes for fast queries
- Query by resource type, department, or account
- Advanced filtering (region, date range, tags)
- Export to CSV with pandas integration
- Cost analysis reports with visualizations
-
Monitoring & Observability:
- CloudWatch dashboard with 15+ metrics
- Cost threshold alarms (configurable)
- Collection failure detection
- Performance tracking (duration, resource count)
- Error rate monitoring
-
Automated Actions:
- Multiple Lambda actions (collect, analyze, check, cleanup)
- Failed collection tracking and retry
- Automated report generation to S3
- Email and Slack notifications
- Stale resource identification
- AWS CLI configured with appropriate credentials
- Python 3.9+ with pip
- Central AWS account for deployment
- AWS Organizations (optional but recommended)
- Email address for notifications
git clone <repository-url>
cd aws-multi-account-inventoryCopy and update the configuration file:
cp config/accounts.json.example config/accounts.jsonEdit config/accounts.json with enhanced configuration:
{
"accounts": {
"engineering": {
"account_id": "123456789012",
"role_name": "AWSInventoryRole",
"enabled": true
},
"marketing": {
"account_id": "234567890123",
"role_name": "AWSInventoryRole",
"enabled": true
}
},
"resource_types": ["ec2", "rds", "s3", "lambda"],
"excluded_regions": ["ap-south-2", "ap-southeast-4"],
"collection_settings": {
"parallel_regions": 10,
"timeout_seconds": 300,
"retry_attempts": 3
},
"cost_thresholds": {
"expensive_resource_monthly": 100,
"idle_resource_days": 30,
"stale_resource_days": 90
},
"notifications": {
"sns_topic_arn": "",
"email_on_failure": true,
"slack_webhook_url": ""
}
}Use the provided deployment script for a complete setup:
# Make script executable
chmod +x deploy.sh
# Run full deployment
./deploy.sh
# The script will:
# - Check prerequisites
# - Create S3 artifacts bucket
# - Install dependencies
# - Run tests
# - Package Lambda function and layer
# - Deploy CloudFormation stack
# - Configure monitoring and alertsFor each member account, deploy the cross-account role:
# Using CloudFormation (in each member account)
aws cloudformation deploy \
--template-file infrastructure/member-account-role.yaml \
--stack-name aws-inventory-role \
--parameter-overrides \
MasterAccountId=YOUR_CENTRAL_ACCOUNT_ID \
ExternalId=inventory-collector \
OrganizationId=YOUR_ORG_ID \
--capabilities CAPABILITY_NAMED_IAM \
--profile MEMBER_ACCOUNT_PROFILE# Test inventory collection
aws lambda invoke \
--function-name aws-inventory-collector \
--payload '{"action": "collect"}' \
output.json
# Check results
cat output.json | python -m json.tool
# View metrics dashboard
aws cloudwatch get-dashboard \
--dashboard-name AWS-Inventory-Dashboard
# Check latest collection status
python -m src.query.inventory_query --action summaryRun inventory collection and queries locally:
# Run collection manually
python -m src.collector.enhanced_main --config config/accounts.json
# Show comprehensive summary
python -m src.query.inventory_query --action summary
# Detailed cost analysis
python -m src.query.inventory_query --action cost
# Security compliance report
python -m src.query.inventory_query --action security
# Find stale resources
python -m src.query.inventory_query --action stale --days 30
# Export filtered data
python -m src.query.inventory_query --action export \
--resource-type ec2_instance \
--department engineering \
--output ec2-engineering.csv
# Query by various filters
python -m src.query.inventory_query --action query \
--resource-type rds_instance \
--region us-east-1 \
--format jsonThe Lambda function supports multiple actions via event payload:
# Inventory collection
aws lambda invoke \
--function-name aws-inventory-collector \
--payload '{"action": "collect"}' \
response.json
# Cost analysis
aws lambda invoke \
--function-name aws-inventory-collector \
--payload '{"action": "cost_analysis"}' \
response.json
# Security compliance check
aws lambda invoke \
--function-name aws-inventory-collector \
--payload '{"action": "security_check"}' \
response.json
# Stale resource cleanup check
aws lambda invoke \
--function-name aws-inventory-collector \
--payload '{"action": "cleanup", "days": 90}' \
response.json# Get top 10 most expensive resources
python -m src.query.inventory_query --action cost --format table | head -20
# Find all unencrypted resources
python -m src.query.inventory_query --action security | grep -i "unencrypted"
# Export cost report for finance
python -m src.query.inventory_query --action cost-report \
--output monthly-costs-$(date +%Y%m).csv
# Department-specific analysis
python -m src.query.inventory_query --action query \
--department marketing \
--format json | jq '.[] | select(.estimated_monthly_cost > 50)'Set these for Lambda function:
DYNAMODB_TABLE_NAME=aws-inventory
SNS_TOPIC_ARN=arn:aws:sns:region:account:topic
REPORT_BUCKET=aws-inventory-reports-account
MONTHLY_COST_THRESHOLD=10000
EXTERNAL_ID=inventory-collector
CONFIG_PATH=/opt/config/accounts.json # Optional override for collector configCONFIG_PATH allows you to point the Lambda function to a custom
accounts.json file. If omitted, /opt/config/accounts.json is used.
The accounts.json file supports these settings:
{
"accounts": {
"account_name": {
"account_id": "123456789012",
"role_name": "AWSInventoryRole",
"enabled": true,
"tags": {
"Department": "Engineering",
"CostCenter": "1001"
}
}
},
"resource_types": ["ec2", "rds", "s3", "lambda"],
"excluded_regions": ["ap-south-2"],
"collection_settings": {
"parallel_regions": 10,
"timeout_seconds": 300,
"retry_attempts": 3,
"batch_size": 25
},
"cost_thresholds": {
"expensive_resource_monthly": 100,
"total_monthly_alert": 10000
}
}Configure different schedules in CloudFormation:
Parameters:
CollectionSchedule:
Default: 'rate(12 hours)'
CostAnalysisSchedule:
Default: 'cron(0 8 * * ? *)' # Daily at 8 AM
SecurityCheckSchedule:
Default: 'cron(0 10 * * MON *)' # Weekly on Monday
CleanupSchedule:
Default: 'cron(0 6 1 * ? *)' # Monthly on the 1st- Add collection method to
src/collector/enhanced_main.py:
def _collect_new_resource(self, session, account_id, account_name, region):
"""Collect new resource type with retry logic"""
resources = []
try:
client = session.client('service-name', region_name=region)
# Use pagination
paginator = client.get_paginator('describe_resources')
for page in paginator.paginate():
for resource in page['Resources']:
resources.append({
'resource_type': 'new_resource',
'resource_id': resource['ResourceId'],
'account_id': account_id,
'account_name': account_name,
'department': account_name, # For GSI
'region': region,
'timestamp': datetime.now(timezone.utc).isoformat(),
'attributes': {
'name': resource.get('Name'),
'state': resource.get('State'),
'tags': self._process_tags(resource.get('Tags', []))
},
'estimated_monthly_cost': self._estimate_cost(
'new_resource',
resource
)
})
except Exception as e:
logger.error(f"Error collecting new resources in {region}: {str(e)}")
return resources- Add cost estimation in
_estimate_cost():
elif resource_type == 'new_resource':
# Add resource-specific pricing logic
base_rate = 0.10 # per hour
if attributes.get('type') == 'large':
base_rate = 0.20
return base_rate * 730 # monthly- Update collection orchestration:
# In collect_inventory() method
if 'new_resource' in self.resource_types:
for region in regions:
futures.append(
executor.submit(
self._collect_new_resource,
session, account_id, account_name, region
)
)- Update IAM policies in
infrastructure/member-account-role.yaml:
- Effect: Allow
Action:
- service:DescribeResources
- service:ListResources
- service:GetResourceTags
Resource: '*'- Add unit tests:
@mock_service
def test_collect_new_resource(self, collector):
"""Test new resource collection"""
# Mock service responses
# Assert collection results
# Verify cost calculation- Deploy changes:
# Run tests first
pytest tests/unit/test_enhanced_collector.py::test_collect_new_resource
# Deploy
./deploy.sh- Add to query tool in
src/query/inventory_query.py:
def get_resources_by_custom_filter(self, filter_key, filter_value):
"""Query by custom attribute"""
response = self.table.scan(
FilterExpression=Attr(f'attributes.{filter_key}').eq(filter_value)
)
return self._process_items(response['Items'])- Add CLI option:
@click.option('--custom-filter', nargs=2, help='Custom attribute filter')
def main(..., custom_filter):
if custom_filter:
resources = query.get_resources_by_custom_filter(*custom_filter)Monthly cost breakdown for the inventory system itself:
| Component | Estimated Cost | Notes |
|---|---|---|
| Lambda Execution | $2-5 | All scheduled functions, ~20K invocations |
| DynamoDB | $5-15 | On-demand pricing, includes GSIs |
| CloudWatch Logs | $2-3 | 30-day retention |
| CloudWatch Metrics | $3-5 | Custom metrics and dashboards |
| S3 Reports | $1-2 | Compressed reports with lifecycle |
| SNS Notifications | <$1 | Email and API calls |
| Total | $15-30/month | For organizations with <1000 resources |
# Daily cost analysis with trends
aws lambda invoke \
--function-name aws-inventory-collector \
--payload '{"action": "cost_analysis"}' \
response.json
# Results include:
# - Total monthly spend by service
# - Top 20 most expensive resources
# - Cost trends and projections
# - Savings opportunitiesIdle Resource Detection:
- EC2 instances stopped >30 days
- RDS instances with no connections
- Empty S3 buckets >90 days old
- Lambda functions with <10 invocations/month
Right-sizing Recommendations:
- Oversized EC2 instances (t3.2xlarge with <10% CPU)
- Over-provisioned RDS instances
- Lambda functions with excessive memory
Example Query:
# Find all optimization opportunities
python -m src.query.inventory_query --action cost
# Sample output:
# Idle Resources (15 found):
# - EC2: i-abc123 (stopped 45 days) - Save $50/month
# - RDS: db-prod (0 connections) - Save $200/month
#
# Total Potential Savings: $1,250/monthTrack costs by department or cost center:
# Department breakdown
python -m src.query.inventory_query --action summary --format json | \
jq '.cost_by_department'
# Generate department report
python -m src.query.inventory_query --action export \
--department engineering \
--output engineering-costs.csvConfigure automated responses to cost events:
# In CloudFormation parameters:
CostThresholds:
MonthlyLimit: 10000
ResourceLimit: 500
IdleResourceAction: "notify" # or "stop"-
Weekly Review:
# Run comprehensive cost analysis ./scripts/weekly-cost-review.sh -
Monthly Optimization:
# Identify and act on savings python -m src.tools.optimize_resources \ --dry-run \ --min-savings 50 -
Quarterly Planning:
- Review Reserved Instance coverage
- Analyze usage patterns
- Plan capacity changes
# Check current timeout
aws lambda get-function-configuration \
--function-name aws-inventory-collector \
--query Timeout
# Increase timeout (max 900 seconds)
aws lambda update-function-configuration \
--function-name aws-inventory-collector \
--timeout 900Root Causes:
- Too many accounts/regions
- Large number of resources
- Network latency
Solutions:
- Enable parallel region collection
- Reduce regions in config
- Increase Lambda memory (improves CPU)
Test Role Assumption:
# Test from Lambda execution role
aws sts assume-role \
--role-arn arn:aws:iam::123456789012:role/AWSInventoryRole \
--role-session-name test \
--external-id inventory-collector
# If fails, check trust policy
aws iam get-role \
--role-name AWSInventoryRole \
--query 'Role.AssumeRolePolicyDocument'Common Fixes:
- Verify external ID matches
- Check organization ID in trust policy
- Ensure Lambda execution role has AssumeRole permission
Debug Collection:
# Check CloudWatch logs
aws logs tail /aws/lambda/aws-inventory-collector \
--filter-pattern "ERROR" \
--since 1h
# Run targeted collection
python -m src.collector.enhanced_main \
--account-id 123456789012 \
--resource-type ec2 \
--region us-east-1 \
--debugCheck metrics:
aws cloudwatch get-metric-statistics \
--namespace AWS/DynamoDB \
--metric-name UserErrors \
--dimensions Name=TableName,Value=aws-inventory \
--start-time 2024-01-01T00:00:00Z \
--end-time 2024-01-01T23:59:59Z \
--period 3600 \
--statistics SumSolutions:
- Switch to on-demand billing
- Implement exponential backoff
- Batch writes more efficiently
Analyze spending:
# Check Lambda invocations
aws cloudwatch get-metric-statistics \
--namespace AWS/Lambda \
--metric-name Invocations \
--dimensions Name=FunctionName,Value=aws-inventory-collector \
--period 86400 \
--statistics Sum \
--start-time 2024-01-01T00:00:00Z \
--end-time 2024-01-31T23:59:59ZCost reduction:
- Reduce collection frequency
- Limit resource types
- Enable S3 lifecycle policies
┌─────────────┐ ┌──────────────┐ ┌───────────────┐
│ KMS Key │────▶│ DynamoDB │◀────│ Lambda │
│ (Encryption)│ │ Encrypted │ │ (No Internet)│
└─────────────┘ └──────────────┘ └───────┬───────┘
│
┌──────────────┐ │ STS AssumeRole
│ S3 │ │ + External ID
│ Encrypted │ ▼
│ Versioned │ ┌────────────────┐
└──────────────┘ │ Target Account │
│ Read-Only Role │
└────────────────┘
- Principle of Least Privilege: Lambda has only required permissions
- Cross-Account Access: External ID prevents confused deputy
- No Persistent Credentials: Uses temporary STS credentials
- CloudTrail Logging: All API calls are audited
- Encryption at Rest:
- DynamoDB: AWS managed encryption
- S3: AES-256 server-side encryption
- Lambda environment variables: KMS encrypted
- Encryption in Transit: All API calls use TLS 1.2+
- No Sensitive Data: No passwords, keys, or PII collected
Automated Security Checks:
# Weekly security scan results
{
"unencrypted_resources": [
{"type": "rds", "id": "db-prod-1", "risk": "high"},
{"type": "s3", "id": "logs-bucket", "risk": "medium"}
],
"public_resources": [
{"type": "s3", "id": "static-assets", "risk": "low"}
],
"compliance_score": 85
}Security Metrics Dashboard:
- Unencrypted resource count
- Public access violations
- Failed authentication attempts
- Unusual API activity
- No Internet Access: Lambda runs in AWS managed VPC
- VPC Endpoints (optional): Private connectivity to AWS services
- No Inbound Connections: Event-driven architecture
- API Gateway (optional): Rate limiting and authentication
# Monthly security audit script
./scripts/security-audit.sh
# Checks:
# - IAM role permissions
# - Resource encryption status
# - Public access settings
# - Unused roles/policies
# - CloudTrail compliance- Detection: CloudWatch alarms for anomalies
- Containment: Automated Lambda function disable
- Investigation: CloudTrail and VPC Flow Logs
- Recovery: Restore from DynamoDB point-in-time
- Lessons Learned: Update security controls
- AWS Well-Architected: Security pillar alignment
- CIS AWS Foundations: Benchmark compliance
- SOC 2: Audit trail and access controls
- GDPR: No personal data collection
| Metric | Typical Value | Notes |
|---|---|---|
| Collection Time | 2-5 min | For 5 accounts, 10 regions |
| Resources/Second | 50-100 | With parallel collection |
| DynamoDB Write | 1000/sec | Batch write capacity |
| Query Response | <100ms | Using GSI indexes |
| Memory Usage | 256-512MB | Lambda function |
For Large Organizations (>50 accounts):
-
Parallel Execution:
- Use Step Functions for orchestration
- Split accounts into batches
- Multiple Lambda concurrent executions
-
Data Partitioning:
- Partition DynamoDB by date
- Archive old data to S3
- Use DynamoDB streams for real-time processing
-
Performance Tuning:
# config/accounts.json { "collection_settings": { "parallel_regions": 20, # Increase parallelism "batch_size": 50, # Larger DynamoDB batches "timeout_seconds": 600, # Longer timeout "memory_mb": 1024 # More Lambda memory } }
The system includes a comprehensive dashboard with:
- Collection success rate
- Resource count trends
- Cost analysis graphs
- Security compliance score
- Performance metrics
# Collection health
aws cloudwatch get-metric-statistics \
--namespace AWSInventory \
--metric-name CollectionSuccess \
--statistics Average \
--period 3600
# Cost trends
aws cloudwatch get-metric-statistics \
--namespace AWSInventory \
--metric-name TotalMonthlyCost \
--statistics Maximum \
--period 86400| Alert | Threshold | Action |
|---|---|---|
| Collection Failure | 2 consecutive | Email + Slack |
| High Cost | >$10,000/month | Email + Report |
| Security Issues | >10 resources | Email + Ticket |
| Performance | >5 min duration | Investigation |
- Support for 20+ additional AWS services
- Real-time streaming with Kinesis
- Machine learning cost predictions
- Automated remediation actions
- Multi-cloud support (Azure, GCP)
- GraphQL API interface
- Advanced visualization dashboard
- Kubernetes resource tracking
- Container image scanning
- Compliance reporting (SOC2, ISO)
- v2.0 (Current): Enhanced features, cost analysis, security checks
- v1.0: Basic inventory collection
- v0.9: Initial beta release
- Documentation: This README and code comments
- Issues: GitHub Issues for bug reports
- Discussion: GitHub Discussions for questions
- Email: support@example.com (update with your email)
Q: Can this work with AWS Control Tower? A: Yes, deploy the member role as a Control Tower customization.
Q: How do I add custom tags to all resources? A: Modify the collector to merge account-level tags from config.
Q: Can I use this with AWS SSO? A: Yes, configure your AWS CLI with SSO and run locally.
We welcome contributions! Please see our Contributing Guide for details.
# Clone and setup
git clone <repo>
cd aws-multi-account-inventory
# Create virtual environment
python -m venv venv
source venv/bin/activate
# Install dev dependencies
pip install -r requirements-dev.txt
# Run tests
pytest
# Run linting
flake8 src/
black src/ --checkInstall the development dependencies first so coverage plugins are available:
pip install -r requirements-dev.txt
make testmake test runs the entire unit test suite with coverage enabled.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- AWS SDK for Python (Boto3) team
- Open source community contributors
- AWS Well-Architected Framework authors
Note: This is an enhanced version of the AWS Multi-Account Inventory System with advanced features for cost optimization, security compliance, and intelligent querying. For questions or support, please open an issue on GitHub.