| Resource Type | Count |
|---|---|
| account | 6 |
| security-group | 1 |
| ec2 | 1 |
| ebs | 4 |
| snapshot | 2 |
| cloudwatch | 1 |
| lambda | 1 |
| eip | 2 |
| nat | 1 |
| elb | 3 |
| rds | 2 |
Estimated Monthly Cost: $448.75
Data Sources:
📊 Live Metrics (CloudWatch, 7d)
💰 Cost Explorer Data
CW Queries: 15/12/3
Historical Spend (2025-11-01 → 2025-12-01): $1240.55
| Service | Cost (USD) |
|---|---|
| Amazon EC2 | $530.17 |
| Amazon RDS | $260.34 |
| AWS NAT Gateway | $95.0 |
| Amazon S3 | $93.32 |
| Amazon CloudWatch | $44.9 |
| Region | Cost (USD) |
|---|---|
| us-east-1 | $895.58 |
| us-west-2 | $128.15 |
Potential monthly savings: $302.90. Current cost (flagged resources): $448.75. Primary drivers: underutilized_rds(prod-postgres-db): $78.00, idle_ec2(i-0123456789abcdef0): $62.40, unattached_ebs(vol-fedcba9876543210): $50.00, old_snapshot(snap-old123456789abc): $25.00, idle_elb(my-alb-prod): $25.00
A quick view of baseline security controls (pass/fail/unknown). See Security Posture Findings for details.
| Control | Status |
|---|---|
| Root MFA + no root keys | FAIL |
| Password policy baseline | FAIL |
| Access key hygiene | PASS |
| S3 account public access block | FAIL |
| S3 default encryption | PASS |
| EBS encryption by default | PASS |
| CloudTrail baseline | FAIL |
| AWS Config recorder | PASS |
| GuardDuty enabled | FAIL |
| Security Hub enabled | PASS |
| CloudWatch alarms present | PASS |
| Type | Resource | Region | Est. Savings (USD/mo) | Recommended Action |
|---|---|---|---|---|
| underutilized_rds | prod-postgres-db | us-east-1 | $78.0 | review-and-rightsize |
| idle_ec2 | i-0123456789abcdef0 | us-east-1 | $62.4 | consider-stopping-or-rightsize |
| unattached_ebs | vol-fedcba9876543210 | us-west-2 | $50.0 | snapshot-and-delete |
| old_snapshot | snap-old123456789abc | us-west-2 | $25.0 | delete-snapshot |
| idle_elb | my-alb-prod | us-east-1 | $25.0 | review-and-delete-if-unused |
| Resource Type | Count |
|---|---|
| account | 6 |
| security-group | 1 |
| ec2 | 1 |
| ebs | 4 |
| snapshot | 2 |
| cloudwatch | 1 |
| lambda | 1 |
| eip | 2 |
| nat | 1 |
| elb | 3 |
| rds | 2 |
| Type | Resource | Region | Severity | Est. Cost (USD/mo) | Est. Savings (USD/mo) | Recommended Action | Estimation |
|---|---|---|---|---|---|---|---|
| underutilized_rds 75% Skipped |
prod-postgres-db
db.t3.large → db.t3.medium |
us-east-1 | MEDIUM | 156.0 | 78.0 |
review-and-rightsize
Metric query skipped due to budget exhaustion |
Heuristic |
| idle_ec2 95% Measured | i-0123456789abcdef0 | us-east-1 | CRITICAL | 62.4 | 62.4 |
consider-stopping-or-rightsize
Low CPU and low network usage over the lookback window. |
Heuristic |
| unattached_ebs 50% | vol-fedcba9876543210 | us-west-2 | HIGH | 50.0 | 50.0 |
snapshot-and-delete
EBS volume (500 GB) unattached for >7 days. Create snapshot for safety, then delete if unused. |
Heuristic |
| idle_elb 90% Measured | my-alb-prod | us-east-1 | HIGH | 25.0 | 25.0 |
review-and-delete-if-unused
Low request count over the lookback window. Verify (AWS CLI)aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB --metric-name RequestCount --dimensions Name=LoadBalancer,Value=app/my-alb-prod/1234567890abcdef --start-time $(date -u -v-7d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 86400 --statistics Sum
|
Heuristic |
| old_snapshot 50% |
snap-old123456789abc
964 days old |
us-west-2 | MEDIUM | 25.0 | 25.0 |
delete-snapshot
Snapshot is older than the retention window. Verify (AWS CLI)aws ec2 describe-snapshots --snapshot-ids snap-old123456789abc
|
Heuristic |
| lb_empty_target_groups 90% | old-api-alb | us-west-2 | MEDIUM | 22.5 | 22.5 |
delete-if-unused
APPLICATION load balancer with 2 target group(s), all empty (0 registered targets). Likely leftover from decommissioned application. Verify (AWS CLI)# Check load balancer details
aws elbv2 describe-load-balancers --names old-api-alb --region us-west-2
# List all target groups
aws elbv2 describe-target-groups --load-balancer-arn arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/old-api-alb/abc123 --region us-west-2
# Check target health for each group (example for first group)
aws elbv2 describe-target-health --target-group-arn arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/api-backend-tg/xyz789 --region us-west-2
aws elbv2 describe-target-health --target-group-arn arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/api-frontend-tg/xyz790 --region us-west-2
|
Heuristic |
| unattached_ebs 50% | vol-0123456789abcdef | us-east-1 | HIGH | 10.0 | 10.0 |
snapshot-and-delete
EBS volume (100 GB) unattached for >7 days. Create snapshot for safety, then delete if unused. |
Heuristic |
| cloudwatch_logs_retention 50% | /aws/lambda/data-processor | us-east-1 | MEDIUM | 15.0 | 9.0 |
set-logs-retention
Log group retention is set to Never Expire; set retention to reduce storage costs. Verify (AWS CLI)aws logs describe-log-groups --log-group-name-prefix /aws/lambda/data-processor --query 'logGroups[0].retentionInDays'
|
Heuristic |
| gp2_to_gp3_migration 50% | vol-1111222233334444 | us-east-1 | MEDIUM | 25.0 | 5.0 |
migrate-to-gp3
gp3 is typically cheaper than gp2 for similar workloads. Verify (AWS CLI)aws ec2 describe-volumes --volume-ids vol-1111222233334444 --query 'Volumes[0].VolumeType'
aws ec2 describe-volumes-modifications --volume-id vol-1111222233334444
|
Heuristic |
| old_snapshot 50% |
snap-0123456789abcdef
668 days old |
us-east-1 | LOW | 5.0 | 5.0 |
delete-snapshot
Snapshot is older than the retention window. |
Heuristic |
| unused_eip 50% | 54.123.45.67 | us-east-1 | MEDIUM | 4.0 | 4.0 |
release-eip
Elastic IP is not attached to any instance; AWS charges for unused EIPs. Verify (AWS CLI)aws ec2 describe-addresses --public-ips 54.123.45.67
|
Heuristic |
| unused_eip 50% | 34.210.98.76 | us-west-2 | MEDIUM | 4.0 | 4.0 |
release-eip
Elastic IP is not attached to any instance; AWS charges for unused EIPs. Verify (AWS CLI)aws ec2 describe-addresses --public-ips 34.210.98.76
|
Heuristic |
| overprovisioned_lambda 50% | data-transformer | us-east-1 | LOW | 12.0 | 3.0 |
reduce-lambda-memory
Execution time indicates memory may be over-provisioned. Estimated using measured invocations (~36,000/mo). Verify (AWS CLI)aws lambda get-function-configuration --function-name data-transformer --query 'MemorySize'
aws cloudwatch get-metric-statistics --namespace AWS/Lambda --metric-name Duration --dimensions Name=FunctionName,Value=data-transformer --start-time $(date -u -v-7d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 3600 --statistics Average
|
Heuristic |
| ebs_overprovisioned_performance 50% | vol-5555666677778888 | us-east-1 | MEDIUM | None | None |
reduce-provisioned-performance
Provisioned performance appears high for observed usage over 14 days. Avg IOPS 120 (prov 9000), avg throughput 5.2 MB/s (prov 250 MB/s). Verify (AWS CLI)aws ec2 describe-volumes --volume-ids vol-5555666677778888
aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeReadOps --dimensions Name=VolumeId,Value=vol-5555666677778888 --start-time $(date -u -v-14d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 300 --statistics Sum
aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeWriteOps --dimensions Name=VolumeId,Value=vol-5555666677778888 --start-time $(date -u -v-14d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 300 --statistics Sum
aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeReadBytes --dimensions Name=VolumeId,Value=vol-5555666677778888 --start-time $(date -u -v-14d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 300 --statistics Sum
aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeWriteBytes --dimensions Name=VolumeId,Value=vol-5555666677778888 --start-time $(date -u -v-14d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 300 --statistics Sum
|
Heuristic |
| idle_elb 35% No Data CloudWatch returned no datapoints for the lookback window. | my-alb-staging | us-west-2 | LOW | 0.0 | 0.0 |
review-lb-usage
Unable to measure load balancer request volume: CloudWatch returned no datapoints for the lookback window. Verify (AWS CLI)aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB --metric-name RequestCount --dimensions Name=LoadBalancer,Value=app/my-alb-staging/abcdef1234567890 --start-time $(date -u -v-7d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 86400 --statistics Sum
|
Heuristic |
| nat_gateway 70% | nat-0123456789abcdef | us-east-1 | LOW | 32.85 | 0.0 |
review-and-consider-vpc-endpoint
NAT gateways have hourly + data processing cost; if used primarily for S3, consider an S3 VPC endpoint. Verify (AWS CLI)aws ec2 describe-nat-gateways --nat-gateway-ids nat-0123456789abcdef
aws ec2 describe-route-tables --filters Name=route.nat-gateway-id,Values=nat-0123456789abcdef
|
📐 Estimation Method: base_hourly+uptime • Adjusted cost: $32.85/mo • Assumptions: ['no data processing included', 'uptime estimated from CreateTime', 'regional multiplier applied via pricing tables'] |
| underutilized_rds 30% Access Denied Missing CloudWatch permissions to read metrics for this resource. |
analytics-postgres-db
db.t3.large |
us-east-1 | LOW | 0.0 | 0.0 |
review-and-rightsize
Unable to measure CPU/storage utilization: missing CloudWatch permissions (AccessDenied). |
Heuristic |
| Type | Resource | Region | Severity | Est. Cost (USD/mo) | Est. Savings (USD/mo) | Recommended Action | Estimation |
|---|---|---|---|---|---|---|---|
| iam_root_access_keys_present 100% | root | None | CRITICAL | None | 0 |
remove-root-access-keys
Root access keys are present. Delete root access keys and use least-privilege IAM roles/users. |
Heuristic |
| iam_root_mfa_disabled 100% | root | None | CRITICAL | None | 0 |
enable-root-mfa
Root MFA is not enabled. Enable MFA on the root user and restrict root usage. |
Heuristic |
| cloudtrail_not_configured 90% | account | None | HIGH | None | 0 |
enable-cloudtrail
CloudTrail baseline appears incomplete. Enable an org/account trail (multi-region) and ensure it is actively logging. |
Heuristic |
| s3_account_public_access_block_disabled 90% | account | None | HIGH | None | 0 |
enable-s3-block-public-access
S3 account-level Block Public Access is not fully enabled. |
Heuristic |
| sg_open_to_world 95% | sg-0123abcd4567efgh8 | us-east-1 | HIGH | None | 0 |
restrict-security-group-ingress
Security group allows inbound access from the public internet on sensitive ports. |
Heuristic |
| guardduty_not_enabled 80% | account | None | MEDIUM | None | 0 |
enable-guardduty
GuardDuty is not enabled in one or more regions checked. |
Heuristic |
| iam_password_policy_weak 95% | account | None | MEDIUM | None | 0 |
harden-iam-password-policy
IAM password policy is weaker than common baseline settings. |
Heuristic |
| Type | Resource | Region | Severity | Est. Cost (USD/mo) | Est. Savings (USD/mo) | Recommended Action | Estimation |
|---|---|---|---|---|---|---|---|
| iam_root_mfa_disabled 100% | root | None | CRITICAL | None | 0 |
enable-root-mfa
Root MFA is not enabled. Enable MFA on the root user and restrict root usage. |
Heuristic |
| iam_root_access_keys_present 100% | root | None | CRITICAL | None | 0 |
remove-root-access-keys
Root access keys are present. Delete root access keys and use least-privilege IAM roles/users. |
Heuristic |
| iam_password_policy_weak 95% | account | None | MEDIUM | None | 0 |
harden-iam-password-policy
IAM password policy is weaker than common baseline settings. |
Heuristic |
| cloudtrail_not_configured 90% | account | None | HIGH | None | 0 |
enable-cloudtrail
CloudTrail baseline appears incomplete. Enable an org/account trail (multi-region) and ensure it is actively logging. |
Heuristic |
| s3_account_public_access_block_disabled 90% | account | None | HIGH | None | 0 |
enable-s3-block-public-access
S3 account-level Block Public Access is not fully enabled. |
Heuristic |
| sg_open_to_world 95% | sg-0123abcd4567efgh8 | us-east-1 | HIGH | None | 0 |
restrict-security-group-ingress
Security group allows inbound access from the public internet on sensitive ports. |
Heuristic |
| guardduty_not_enabled 80% | account | None | MEDIUM | None | 0 |
enable-guardduty
GuardDuty is not enabled in one or more regions checked. |
Heuristic |
| idle_ec2 95% Measured | i-0123456789abcdef0 | us-east-1 | CRITICAL | 62.4 | 62.4 |
consider-stopping-or-rightsize
Low CPU and low network usage over the lookback window. |
Heuristic |
| unattached_ebs 50% | vol-0123456789abcdef | us-east-1 | HIGH | 10.0 | 10.0 |
snapshot-and-delete
EBS volume (100 GB) unattached for >7 days. Create snapshot for safety, then delete if unused. |
Heuristic |
| unattached_ebs 50% | vol-fedcba9876543210 | us-west-2 | HIGH | 50.0 | 50.0 |
snapshot-and-delete
EBS volume (500 GB) unattached for >7 days. Create snapshot for safety, then delete if unused. |
Heuristic |
| gp2_to_gp3_migration 50% | vol-1111222233334444 | us-east-1 | MEDIUM | 25.0 | 5.0 |
migrate-to-gp3
gp3 is typically cheaper than gp2 for similar workloads. Verify (AWS CLI)aws ec2 describe-volumes --volume-ids vol-1111222233334444 --query 'Volumes[0].VolumeType'
aws ec2 describe-volumes-modifications --volume-id vol-1111222233334444
|
Heuristic |
| ebs_overprovisioned_performance 50% | vol-5555666677778888 | us-east-1 | MEDIUM | None | None |
reduce-provisioned-performance
Provisioned performance appears high for observed usage over 14 days. Avg IOPS 120 (prov 9000), avg throughput 5.2 MB/s (prov 250 MB/s). Verify (AWS CLI)aws ec2 describe-volumes --volume-ids vol-5555666677778888
aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeReadOps --dimensions Name=VolumeId,Value=vol-5555666677778888 --start-time $(date -u -v-14d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 300 --statistics Sum
aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeWriteOps --dimensions Name=VolumeId,Value=vol-5555666677778888 --start-time $(date -u -v-14d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 300 --statistics Sum
aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeReadBytes --dimensions Name=VolumeId,Value=vol-5555666677778888 --start-time $(date -u -v-14d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 300 --statistics Sum
aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeWriteBytes --dimensions Name=VolumeId,Value=vol-5555666677778888 --start-time $(date -u -v-14d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 300 --statistics Sum
|
Heuristic |
| old_snapshot 50% |
snap-old123456789abc
964 days old |
us-west-2 | MEDIUM | 25.0 | 25.0 |
delete-snapshot
Snapshot is older than the retention window. Verify (AWS CLI)aws ec2 describe-snapshots --snapshot-ids snap-old123456789abc
|
Heuristic |
| cloudwatch_logs_retention 50% | /aws/lambda/data-processor | us-east-1 | MEDIUM | 15.0 | 9.0 |
set-logs-retention
Log group retention is set to Never Expire; set retention to reduce storage costs. Verify (AWS CLI)aws logs describe-log-groups --log-group-name-prefix /aws/lambda/data-processor --query 'logGroups[0].retentionInDays'
|
Heuristic |
| overprovisioned_lambda 50% | data-transformer | us-east-1 | LOW | 12.0 | 3.0 |
reduce-lambda-memory
Execution time indicates memory may be over-provisioned. Estimated using measured invocations (~36,000/mo). Verify (AWS CLI)aws lambda get-function-configuration --function-name data-transformer --query 'MemorySize'
aws cloudwatch get-metric-statistics --namespace AWS/Lambda --metric-name Duration --dimensions Name=FunctionName,Value=data-transformer --start-time $(date -u -v-7d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 3600 --statistics Average
|
Heuristic |
| unused_eip 50% | 54.123.45.67 | us-east-1 | MEDIUM | 4.0 | 4.0 |
release-eip
Elastic IP is not attached to any instance; AWS charges for unused EIPs. Verify (AWS CLI)aws ec2 describe-addresses --public-ips 54.123.45.67
|
Heuristic |
| unused_eip 50% | 34.210.98.76 | us-west-2 | MEDIUM | 4.0 | 4.0 |
release-eip
Elastic IP is not attached to any instance; AWS charges for unused EIPs. Verify (AWS CLI)aws ec2 describe-addresses --public-ips 34.210.98.76
|
Heuristic |
| nat_gateway 70% | nat-0123456789abcdef | us-east-1 | LOW | 32.85 | 0.0 |
review-and-consider-vpc-endpoint
NAT gateways have hourly + data processing cost; if used primarily for S3, consider an S3 VPC endpoint. Verify (AWS CLI)aws ec2 describe-nat-gateways --nat-gateway-ids nat-0123456789abcdef
aws ec2 describe-route-tables --filters Name=route.nat-gateway-id,Values=nat-0123456789abcdef
|
📐 Estimation Method: base_hourly+uptime • Adjusted cost: $32.85/mo • Assumptions: ['no data processing included', 'uptime estimated from CreateTime', 'regional multiplier applied via pricing tables'] |
| lb_empty_target_groups 90% | old-api-alb | us-west-2 | MEDIUM | 22.5 | 22.5 |
delete-if-unused
APPLICATION load balancer with 2 target group(s), all empty (0 registered targets). Likely leftover from decommissioned application. Verify (AWS CLI)# Check load balancer details
aws elbv2 describe-load-balancers --names old-api-alb --region us-west-2
# List all target groups
aws elbv2 describe-target-groups --load-balancer-arn arn:aws:elasticloadbalancing:us-west-2:123456789012:loadbalancer/app/old-api-alb/abc123 --region us-west-2
# Check target health for each group (example for first group)
aws elbv2 describe-target-health --target-group-arn arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/api-backend-tg/xyz789 --region us-west-2
aws elbv2 describe-target-health --target-group-arn arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/api-frontend-tg/xyz790 --region us-west-2
|
Heuristic |
| idle_elb 90% Measured | my-alb-prod | us-east-1 | HIGH | 25.0 | 25.0 |
review-and-delete-if-unused
Low request count over the lookback window. Verify (AWS CLI)aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB --metric-name RequestCount --dimensions Name=LoadBalancer,Value=app/my-alb-prod/1234567890abcdef --start-time $(date -u -v-7d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 86400 --statistics Sum
|
Heuristic |
| idle_elb 35% No Data CloudWatch returned no datapoints for the lookback window. | my-alb-staging | us-west-2 | LOW | 0.0 | 0.0 |
review-lb-usage
Unable to measure load balancer request volume: CloudWatch returned no datapoints for the lookback window. Verify (AWS CLI)aws cloudwatch get-metric-statistics --namespace AWS/ApplicationELB --metric-name RequestCount --dimensions Name=LoadBalancer,Value=app/my-alb-staging/abcdef1234567890 --start-time $(date -u -v-7d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 86400 --statistics Sum
|
Heuristic |
| underutilized_rds 75% Skipped |
prod-postgres-db
db.t3.large → db.t3.medium |
us-east-1 | MEDIUM | 156.0 | 78.0 |
review-and-rightsize
Metric query skipped due to budget exhaustion |
Heuristic |
| underutilized_rds 30% Access Denied Missing CloudWatch permissions to read metrics for this resource. |
analytics-postgres-db
db.t3.large |
us-east-1 | LOW | 0.0 | 0.0 |
review-and-rightsize
Unable to measure CPU/storage utilization: missing CloudWatch permissions (AccessDenied). |
Heuristic |
| old_snapshot 50% |
snap-0123456789abcdef
668 days old |
us-east-1 | LOW | 5.0 | 5.0 |
delete-snapshot
Snapshot is older than the retention window. |
Heuristic |
📁 CSV export available: findings.csv generated alongside this report.