Find unused AWS resources costing you money. No admin, no auth, just results.
Project description
Open AWS Scanner
Find unused AWS resources costing you money. No admin panel, no Keycloak, no multi-tenant complexity — just point it at your AWS account and find waste.
Table of Contents
- Install
- How To: Run Your First Scan
- How To: Set Up AWS Credentials
- How To: Scan Multiple Regions
- How To: Run as a Server
- How To: Use the API
- How To: Run with Docker
- How To: Use Stage Mode (No AWS Needed)
- How To: Track Savings
- How To: Use Postgres Instead of SQLite
- How To: Set Up the IAM Role
- How To: Verify Package Signatures
- CLI Reference
- API Reference
- What It Scans
- Configuration Reference
- Relationship to CostOps Platform
- License
Install
pip install open-aws-scanner
Or from source:
git clone https://github.com/yourusername/open-aws-scanner.git
cd open-aws-scanner
pip install .
For development (editable install):
pip install -e .
How To: Run Your First Scan
Step 1 — Create a config file:
open-aws-scanner init
This creates config.env in your current directory.
Step 2 — Edit config.env with your AWS setup (see credentials section below).
Step 3 — Run a scan:
open-aws-scanner scan
Output looks like:
================================================================================
Open AWS Scanner — 10 issues found | $472.90/mo potential savings
================================================================================
EC2_Instance (2 found, $227.50/mo)
• test-server-bob: CPU avg 2.1% over last 7 days $85.00/mo [us-east-1]
• legacy-worker-node: CPU avg 1.3% over last 7 days $142.50/mo [us-east-1]
EBS_Volume (2 found, $76.80/mo)
• backup-vol-old: Volume is not attached to any instance $64.00/mo [us-east-1]
• dev-data-volume: Volume is not attached to any instance $12.80/mo [us-east-1]
────────────────────────────────────────────────────────────────────────────────
Total potential savings: $472.90/month
Step 4 — Get JSON output (for piping to other tools):
open-aws-scanner scan --output json > findings.json
How To: Set Up AWS Credentials
The scanner needs read-only AWS access. Pick one of these methods:
Option A: Use your existing AWS profile (simplest)
Leave AWS_ROLE_ARN blank in config.env. The scanner uses whatever credentials are available:
~/.aws/credentials(AWS CLI profile)AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYenvironment variables- EC2 instance profile or ECS task role
AWS_ROLE_ARN=
AWS_REGIONS=us-east-1
Option B: Assume an IAM role (cross-account)
Set up a read-only role in the target account (see IAM setup), then:
AWS_ROLE_ARN=arn:aws:iam::123456789012:role/OpenScannerRole
AWS_EXTERNAL_ID=my-scanner
AWS_REGIONS=us-east-1
Option C: Explicit credentials
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=wJal...
AWS_REGIONS=us-east-1
Not recommended for production — use roles or profiles instead.
Verify credentials work
# Quick check that your credentials are valid
aws sts get-caller-identity
How To: Scan Multiple Regions
Comma-separate regions in config.env:
AWS_REGIONS=us-east-1,us-west-2,eu-west-1,ap-southeast-1
Or override from CLI:
open-aws-scanner scan --regions us-east-1,eu-west-1
Each region is scanned independently. Findings are tagged with their region.
How To: Run as a Server
Start the API server with automatic scheduled scans:
open-aws-scanner serve
This gives you:
- Automatic scans every N hours (default: 6, configurable via
SCAN_INTERVAL_HOURS) - REST API for querying findings
- JSON index page at
/with status summary - Swagger docs at
/docs
Custom host/port:
open-aws-scanner serve --host 127.0.0.1 --port 9000
Keep it running in the background:
nohup open-aws-scanner serve > scanner.log 2>&1 &
Or with systemd (Linux):
# /etc/systemd/system/open-aws-scanner.service
[Unit]
Description=Open AWS Scanner
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/scanner
ExecStart=/usr/local/bin/open-aws-scanner serve
Restart=always
Environment=AWS_REGIONS=us-east-1
[Install]
WantedBy=multi-user.target
How To: Use the API
Once the server is running:
Trigger a scan
curl -X POST http://localhost:8000/scan
Get all findings
curl http://localhost:8000/findings
Filter findings
# Only open findings
curl "http://localhost:8000/findings?status=open"
# Only EC2 findings
curl "http://localhost:8000/findings?resource_type=EC2_Instance"
# Combine filters
curl "http://localhost:8000/findings?status=open&resource_type=EBS_Volume"
Mark a finding as fixed
curl -X PUT "http://localhost:8000/findings/42/status?status=fixed"
Valid statuses: open, fixed, dismissed, in_progress
Get savings summary
curl http://localhost:8000/summary
Response:
{
"total_findings": 10,
"total_potential_savings": 472.90,
"open": { "count": 7, "savings": 380.50 },
"fixed": { "count": 2, "savings_realized": 67.65 },
"dismissed": { "count": 0, "savings": 0 },
"in_progress": { "count": 1, "savings": 24.75 }
}
Get scan history
curl http://localhost:8000/scans
How To: Run with Docker
Build and run
docker build -t open-aws-scanner .
docker run -p 8000:8000 --env-file config.env open-aws-scanner
With AWS credentials from host
docker run -p 8000:8000 \
-e AWS_REGIONS=us-east-1 \
-e STAGE_MODE=true \
-v ~/.aws:/root/.aws:ro \
open-aws-scanner
Persist the database
docker run -p 8000:8000 \
--env-file config.env \
-v $(pwd)/data:/app/scanner.db \
open-aws-scanner
How To: Use Stage Mode (No AWS Needed)
Stage mode uses mock data — no real AWS credentials required. Perfect for:
- Testing the API
- Developing integrations
- Demoing the tool
Set in config.env:
STAGE_MODE=true
Or as an environment variable:
STAGE_MODE=true open-aws-scanner scan
STAGE_MODE=true open-aws-scanner serve
How To: Track Savings
The scanner tracks finding status over time. A typical workflow:
- Run a scan → findings are
open - Fix a resource (e.g., delete the unused EBS volume in AWS console)
- Mark it fixed:
curl -X PUT "http://localhost:8000/findings/42/status?status=fixed"
- Check realized savings:
curl http://localhost:8000/summary
# → "fixed": { "count": 3, "savings_realized": 145.25 }
Statuses:
| Status | Meaning |
|---|---|
open |
Identified waste, not yet addressed |
in_progress |
Being worked on |
fixed |
Resource cleaned up — savings realized |
dismissed |
Intentionally kept (not waste) |
How To: Use Postgres Instead of SQLite
By default the scanner uses SQLite (zero config, file-based). For production or team use, switch to Postgres:
Step 1 — Install the postgres extra:
pip install open-aws-scanner[postgres]
Step 2 — Set DATABASE_URL in config.env:
DATABASE_URL=postgresql://user:pass@localhost:5432/scanner
Step 3 — Create the database:
createdb scanner
The tables are created automatically on first run.
How To: Set Up the IAM Role
If scanning a different AWS account, create a read-only role:
Step 1 — In the target account, create a role with this trust policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::YOUR_SCANNER_ACCOUNT:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "my-scanner"
}
}
}
]
}
Step 2 — Attach this permissions policy to the role:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "OpenScannerReadOnly",
"Effect": "Allow",
"Action": [
"ec2:Describe*",
"rds:DescribeDBInstances",
"rds:DescribeDBClusters",
"lambda:ListFunctions",
"lambda:ListProvisionedConcurrencyConfigs",
"s3:ListAllMyBuckets",
"s3:ListBucket",
"sqs:ListQueues",
"sns:ListTopics",
"elasticloadbalancing:DescribeLoadBalancers",
"dynamodb:ListTables",
"elasticache:DescribeCacheClusters",
"logs:DescribeLogGroups",
"logs:DescribeLogStreams",
"logs:StartQuery",
"logs:GetQueryResults",
"secretsmanager:ListSecrets",
"ecs:ListClusters",
"ecs:ListServices",
"autoscaling:DescribeAutoScalingGroups",
"autoscaling:DescribeAutoScalingInstances",
"organizations:ListAccounts",
"organizations:DescribeOrganization",
"organizations:DescribeAccount",
"cloudwatch:GetMetricData",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics",
"cloudtrail:LookupEvents",
"cloudtrail:DescribeTrails",
"cloudtrail:GetTrailStatus",
"cloudtrail:GetEventSelectors",
"ce:GetCostAndUsage",
"ce:GetCostAndUsageWithResources",
"ce:GetCostForecast",
"ce:GetUsageForecast",
"ce:GetDimensionValues",
"ce:GetTags",
"ce:GetCostCategories",
"ce:GetCostAndUsageComparisons",
"ce:GetCostComparisonDrivers",
"ce:GetSavingsPlansCoverage",
"ce:GetSavingsPlansUtilization",
"ce:GetSavingsPlansUtilizationDetails",
"ce:GetSavingsPlansPurchaseRecommendation",
"ce:GetReservationCoverage",
"ce:GetReservationUtilization",
"ce:GetReservationPurchaseRecommendation",
"ce:GetRightsizingRecommendation",
"ce:GetAnomalies",
"ce:GetAnomalyMonitors",
"ce:ListCostAllocationTags",
"ce:ListCostAllocationTagBackfillHistory",
"ce:DescribeCostCategoryDefinition",
"ce:ListCostCategoryDefinitions",
"budgets:ViewBudget",
"cost-optimization-hub:GetRecommendation",
"cost-optimization-hub:ListRecommendations",
"cost-optimization-hub:ListRecommendationSummaries",
"compute-optimizer:DescribeRecommendationExportJobs",
"compute-optimizer:GetEnrollmentStatus",
"compute-optimizer:GetEnrollmentStatusesForOrganization",
"compute-optimizer:GetRecommendationSummaries",
"compute-optimizer:GetEC2InstanceRecommendations",
"compute-optimizer:GetEC2RecommendationProjectedMetrics",
"compute-optimizer:GetAutoScalingGroupRecommendations",
"compute-optimizer:GetEBSVolumeRecommendations",
"compute-optimizer:GetLambdaFunctionRecommendations",
"compute-optimizer:GetRecommendationPreferences",
"compute-optimizer:GetEffectiveRecommendationPreferences",
"compute-optimizer:GetECSServiceRecommendations",
"compute-optimizer:GetECSServiceRecommendationProjectedMetrics",
"compute-optimizer:GetLicenseRecommendations",
"compute-optimizer:GetRDSDatabaseRecommendations",
"compute-optimizer:GetRDSDatabaseRecommendationProjectedMetrics",
"compute-optimizer:GetIdleRecommendations",
"pricing:DescribeServices",
"pricing:GetAttributeValues",
"pricing:GetProducts",
"freetier:GetFreeTierUsage",
"bcm-pricing-calculator:GetPreferences",
"bcm-pricing-calculator:GetWorkloadEstimate",
"bcm-pricing-calculator:ListWorkloadEstimateUsage",
"bcm-pricing-calculator:ListWorkloadEstimates"
],
"Resource": "*"
}
]
}
Step 3 — Configure config.env:
AWS_ROLE_ARN=arn:aws:iam::123456789012:role/OpenScannerRole
AWS_EXTERNAL_ID=my-scanner
Step 4 — Make sure the machine running the scanner has sts:AssumeRole permission for that role ARN.
How To: Verify Package Signatures
All releases are signed with Sigstore.
Verify a release
pip install sigstore
sigstore verify identity open_aws_scanner-0.1.0.tar.gz \
--cert-identity "luge-sud-0q@icloud.com" \
--cert-oidc-issuer "https://appleid.apple.com"
Verify git commits
git log --show-signature
CLI Reference
open-aws-scanner init
Create a config.env template in the current directory.
open-aws-scanner scan [OPTIONS]
Run a one-shot scan and print results.
--regions REGIONS Comma-separated AWS regions (overrides config)
--role-arn ARN AWS role ARN to assume (overrides config)
--output {json,table} Output format (default: table)
--config PATH Path to config.env (default: ./config.env)
open-aws-scanner serve [OPTIONS]
Start the API server with scheduled scans.
--host HOST Host to bind (default: 0.0.0.0)
--port PORT Port to bind (default: 8000)
--config PATH Path to config.env (default: ./config.env)
API Reference
| Method | Endpoint | Description |
|---|---|---|
| GET | / |
Status summary + endpoint list (JSON) |
| GET | /health |
Health check |
| POST | /scan |
Trigger a scan now |
| GET | /findings |
List all findings |
| GET | /findings?status=open |
Filter by status |
| GET | /findings?resource_type=EC2_Instance |
Filter by resource type |
| PUT | /findings/{id}/status?status=fixed |
Update finding status |
| GET | /summary |
Savings summary by status |
| GET | /scans |
Scan run history |
| GET | /docs |
Swagger UI (interactive) |
| GET | /redoc |
ReDoc (readable docs) |
What It Scans
| Resource | Detection | Est. Savings |
|---|---|---|
| EBS Volumes | Unattached | $0.08/GB/mo |
| Elastic IPs | Unassociated | $3.65/mo |
| EBS Snapshots | Orphaned (source volume deleted, 30+ days) | $0.05/GB/mo |
| ENIs | Detached | $0 |
| Security Groups | Not attached to any ENI | $0 |
| EC2 Instances | CPU avg < 5% over 7 days | — |
| RDS Instances | < 1 avg connection over 7 days | — |
| Lambda Functions | Zero invocations in 30 days | $0 |
| S3 Buckets | Empty (zero objects) | $0 |
| SQS Queues | Zero messages sent in 14 days | $0 |
| SNS Topics | Zero publishes in 14 days | $0 |
| Load Balancers | Zero requests in 7 days | $16.20/mo |
| NAT Gateways | Zero bytes processed in 7 days | $32.40/mo |
| DynamoDB Tables | Zero read capacity in 14 days | — |
| ElastiCache | < 1 avg connection in 7 days | — |
| CloudWatch Logs | No ingestion in 30 days | $0.03/GB |
| Secrets Manager | Not accessed in 90 days | $0.40/mo |
Configuration Reference
All settings go in config.env (or as environment variables):
| Variable | Description | Default |
|---|---|---|
AWS_ROLE_ARN |
IAM role to assume (blank = use local creds) | (empty) |
AWS_EXTERNAL_ID |
External ID for role assumption | (empty) |
AWS_REGIONS |
Comma-separated regions to scan | us-east-1 |
AWS_ACCESS_KEY_ID |
Explicit access key (optional) | (from env/profile) |
AWS_SECRET_ACCESS_KEY |
Explicit secret key (optional) | (from env/profile) |
SCAN_INTERVAL_HOURS |
Hours between scheduled scans (server mode) | 6 |
STAGE_MODE |
Use mock data (no real AWS calls) | false |
DATABASE_URL |
Database connection string | sqlite:///./scanner.db |
HOST |
Server bind host | 0.0.0.0 |
PORT |
Server bind port | 8000 |
Relationship to CostOps Platform
This is the open-source core of the CostOps AWS Scanner platform. The full platform adds:
- Multi-tenant support with per-tenant IAM role assumption
- Keycloak SSO with JWT zero-trust auth on every endpoint
- Admin API for tenant/user/billing management
- React dashboard with drag-and-drop tiles
- AWS Cost Explorer integration (rightsizing, RI/Savings Plans utilization)
- Email reports, activity logging, billing model
- 55+ resource scanners (this package has the core 17)
The full scanner imports scanning functions from this package — one codebase, shared logic.
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file open_aws_scanner-0.3.0.tar.gz.
File metadata
- Download URL: open_aws_scanner-0.3.0.tar.gz
- Upload date:
- Size: 35.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d449d5c61530566155d3cf6283306bade34dd94a691cbe9140b5795e5b5bf989
|
|
| MD5 |
5e02c92019985d15968d45e8b21ec3ac
|
|
| BLAKE2b-256 |
635a414e89cc68f94422daacf0eb5cb5554421ea12792008ccb00f027df5b8bd
|
Provenance
The following attestation bundles were made for open_aws_scanner-0.3.0.tar.gz:
Publisher:
publish.yml on cost-ops/open-AWS-scanner
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
open_aws_scanner-0.3.0.tar.gz -
Subject digest:
d449d5c61530566155d3cf6283306bade34dd94a691cbe9140b5795e5b5bf989 - Sigstore transparency entry: 2140147141
- Sigstore integration time:
-
Permalink:
cost-ops/open-AWS-scanner@12853df2aa5f12e0ff46efd67771302eb7177b79 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/cost-ops
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@12853df2aa5f12e0ff46efd67771302eb7177b79 -
Trigger Event:
release
-
Statement type:
File details
Details for the file open_aws_scanner-0.3.0-py3-none-any.whl.
File metadata
- Download URL: open_aws_scanner-0.3.0-py3-none-any.whl
- Upload date:
- Size: 31.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bc40fd41ae7929482122ea028f31b8dde51038ae2250b9ed8ff7e87418787f4d
|
|
| MD5 |
e86e073ec1cb7ed08ccd819408a10b88
|
|
| BLAKE2b-256 |
3ca9920a4bd63d93e0416eb5bf20c5dc1428980c020752240413e7d5ca6272e7
|
Provenance
The following attestation bundles were made for open_aws_scanner-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on cost-ops/open-AWS-scanner
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
open_aws_scanner-0.3.0-py3-none-any.whl -
Subject digest:
bc40fd41ae7929482122ea028f31b8dde51038ae2250b9ed8ff7e87418787f4d - Sigstore transparency entry: 2140147160
- Sigstore integration time:
-
Permalink:
cost-ops/open-AWS-scanner@12853df2aa5f12e0ff46efd67771302eb7177b79 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/cost-ops
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@12853df2aa5f12e0ff46efd67771302eb7177b79 -
Trigger Event:
release
-
Statement type: