cloudleak 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. cloudleak-1.0.0/LICENSE +21 -0
  2. cloudleak-1.0.0/PKG-INFO +518 -0
  3. cloudleak-1.0.0/README.md +494 -0
  4. cloudleak-1.0.0/cloudleak.egg-info/PKG-INFO +518 -0
  5. cloudleak-1.0.0/cloudleak.egg-info/SOURCES.txt +63 -0
  6. cloudleak-1.0.0/cloudleak.egg-info/dependency_links.txt +1 -0
  7. cloudleak-1.0.0/cloudleak.egg-info/entry_points.txt +2 -0
  8. cloudleak-1.0.0/cloudleak.egg-info/requires.txt +15 -0
  9. cloudleak-1.0.0/cloudleak.egg-info/top_level.txt +1 -0
  10. cloudleak-1.0.0/pyproject.toml +62 -0
  11. cloudleak-1.0.0/setup.cfg +4 -0
  12. cloudleak-1.0.0/src/__init__.py +6 -0
  13. cloudleak-1.0.0/src/auth/__init__.py +0 -0
  14. cloudleak-1.0.0/src/auth/session.py +53 -0
  15. cloudleak-1.0.0/src/cli/__init__.py +531 -0
  16. cloudleak-1.0.0/src/config/__init__.py +0 -0
  17. cloudleak-1.0.0/src/config/loader.py +187 -0
  18. cloudleak-1.0.0/src/output/__init__.py +8 -0
  19. cloudleak-1.0.0/src/output/console.py +266 -0
  20. cloudleak-1.0.0/src/output/csv_.py +44 -0
  21. cloudleak-1.0.0/src/output/executive.py +26 -0
  22. cloudleak-1.0.0/src/output/executive_model.py +319 -0
  23. cloudleak-1.0.0/src/output/html_.py +921 -0
  24. cloudleak-1.0.0/src/output/json_.py +76 -0
  25. cloudleak-1.0.0/src/output/templates/executive.html.j2 +506 -0
  26. cloudleak-1.0.0/src/pricing/__init__.py +0 -0
  27. cloudleak-1.0.0/src/pricing/cache.py +172 -0
  28. cloudleak-1.0.0/src/pricing/prices.json +4 -0
  29. cloudleak-1.0.0/src/pricing/update.py +381 -0
  30. cloudleak-1.0.0/src/rules/__init__.py +90 -0
  31. cloudleak-1.0.0/src/rules/aiml.py +1019 -0
  32. cloudleak-1.0.0/src/rules/analytics.py +939 -0
  33. cloudleak-1.0.0/src/rules/appsync.py +140 -0
  34. cloudleak-1.0.0/src/rules/batch.py +154 -0
  35. cloudleak-1.0.0/src/rules/cloudwatch.py +586 -0
  36. cloudleak-1.0.0/src/rules/codebuild.py +139 -0
  37. cloudleak-1.0.0/src/rules/codepipeline.py +135 -0
  38. cloudleak-1.0.0/src/rules/connect.py +123 -0
  39. cloudleak-1.0.0/src/rules/containers.py +754 -0
  40. cloudleak-1.0.0/src/rules/datasync.py +109 -0
  41. cloudleak-1.0.0/src/rules/desktop.py +318 -0
  42. cloudleak-1.0.0/src/rules/ebs.py +866 -0
  43. cloudleak-1.0.0/src/rules/ec2.py +1088 -0
  44. cloudleak-1.0.0/src/rules/gamelift.py +239 -0
  45. cloudleak-1.0.0/src/rules/governance.py +443 -0
  46. cloudleak-1.0.0/src/rules/grafana.py +119 -0
  47. cloudleak-1.0.0/src/rules/ivs.py +116 -0
  48. cloudleak-1.0.0/src/rules/lambda_.py +997 -0
  49. cloudleak-1.0.0/src/rules/lightsail.py +142 -0
  50. cloudleak-1.0.0/src/rules/messaging.py +420 -0
  51. cloudleak-1.0.0/src/rules/networking.py +1378 -0
  52. cloudleak-1.0.0/src/rules/prometheus.py +116 -0
  53. cloudleak-1.0.0/src/rules/rds.py +1542 -0
  54. cloudleak-1.0.0/src/rules/s3.py +905 -0
  55. cloudleak-1.0.0/src/rules/security.py +753 -0
  56. cloudleak-1.0.0/src/rules/transcoder.py +132 -0
  57. cloudleak-1.0.0/src/rules/transfer.py +123 -0
  58. cloudleak-1.0.0/src/scanner/__init__.py +0 -0
  59. cloudleak-1.0.0/src/scanner/cloudwatch.py +154 -0
  60. cloudleak-1.0.0/src/scanner/compute_optimizer.py +82 -0
  61. cloudleak-1.0.0/src/scanner/context.py +21 -0
  62. cloudleak-1.0.0/src/scanner/doctor.py +378 -0
  63. cloudleak-1.0.0/src/scanner/finding.py +70 -0
  64. cloudleak-1.0.0/src/scanner/orchestrator.py +167 -0
  65. cloudleak-1.0.0/src/scanner/rule.py +23 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ragnild
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,518 @@
1
+ Metadata-Version: 2.4
2
+ Name: cloudleak
3
+ Version: 1.0.0
4
+ Summary: Production-grade AWS FinOps CLI scanner
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Requires-Dist: click>=8.1
10
+ Requires-Dist: boto3>=1.34
11
+ Requires-Dist: botocore>=1.34
12
+ Requires-Dist: rich>=13.0
13
+ Requires-Dist: pyyaml>=6.0
14
+ Requires-Dist: jinja2>=3.1
15
+ Provides-Extra: dev
16
+ Requires-Dist: pytest>=8.0; extra == "dev"
17
+ Requires-Dist: pytest-mock>=3.12; extra == "dev"
18
+ Requires-Dist: moto[all]>=5.0; extra == "dev"
19
+ Requires-Dist: ruff>=0.4; extra == "dev"
20
+ Requires-Dist: mypy>=1.10; extra == "dev"
21
+ Requires-Dist: pyinstaller>=6.0; extra == "dev"
22
+ Requires-Dist: build>=1.0; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # cloudleak
26
+
27
+ > Production-grade, read-only AWS FinOps CLI scanner. Find the resources in your AWS account that are spending money for no productive reason.
28
+
29
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
30
+ [![PyPI version](https://img.shields.io/pypi/v/cloudleak.svg)](https://pypi.org/project/cloudleak/)
31
+ [![CI](https://github.com/ragnild/CloudLeak/actions/workflows/ci.yml/badge.svg)](https://github.com/ragnild/CloudLeak/actions/workflows/ci.yml)
32
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://python.org)
33
+
34
+ ---
35
+
36
+ ## What is cloudleak?
37
+
38
+ cloudleak connects to your AWS account using your existing credentials and scans resources across one region or all regions, producing a prioritised finding report. Each finding includes the specific evidence that triggered it, an estimated monthly waste figure, and a concrete recommendation.
39
+
40
+ **143 rules** across EC2, EBS, Networking, RDS, S3, Lambda, CloudWatch, AI/ML, Containers, Security, Governance, Analytics, Messaging, Desktop, and more.
41
+
42
+ ### Why not just use Cost Explorer?
43
+
44
+ Cost Explorer tells you what you spent. cloudleak tells you _why_ you're over-spending and which specific resources to address. It integrates AWS Compute Optimizer right-sizing signals, checks for resources that are running but idle, and catches governance anti-patterns that inflate your bill invisibly (untagged resources, missing lifecycle policies, provisioned throughput sitting empty).
45
+
46
+ ### Key properties
47
+
48
+ - **Read-only always.** Zero mutations. Safe to run in production accounts with least-privilege.
49
+ - **Graceful permission degradation.** `AccessDenied` marks a rule `SKIPPED` — it never crashes.
50
+ - **Offline pricing.** Prices are loaded from a local `prices.json` cache. No live pricing calls during a scan.
51
+ - **Parallel execution.** Rule groups run concurrently via a thread pool.
52
+ - **Configurable thresholds.** Every idle-day window, CPU threshold, and size limit is driven by `cloudleak.yaml`.
53
+ - **No telemetry.** Safe for air-gapped and regulated environments.
54
+
55
+ ---
56
+
57
+ ## Installation
58
+
59
+ AWS credentials must be configured before running a scan (any standard method: SSO, env vars, `~/.aws/credentials`, instance role).
60
+
61
+ ### pip / pipx (recommended)
62
+
63
+ Requires Python 3.10+.
64
+
65
+ ```bash
66
+ # pipx keeps cloudleak isolated from your other Python environments
67
+ pipx install cloudleak
68
+
69
+ # or plain pip
70
+ pip install cloudleak
71
+ ```
72
+
73
+ ### Standalone binary (no Python required)
74
+
75
+ Download the pre-built binary for your OS from [GitHub Releases](https://github.com/ragnild/CloudLeak/releases/latest):
76
+
77
+ | OS | File |
78
+ |---|---|
79
+ | Linux x86-64 | `cloudleak-linux-x86_64` |
80
+ | macOS (Apple Silicon) | `cloudleak-macos-arm64` |
81
+ | Windows x86-64 | `cloudleak-windows-x86_64.exe` |
82
+
83
+ ```bash
84
+ # Linux / macOS — make executable first
85
+ chmod +x cloudleak-linux-x86_64
86
+ ./cloudleak-linux-x86_64 --version
87
+ ```
88
+
89
+ Standalone binaries are the recommended installation for **air-gapped and regulated environments** where `pip install` is not available.
90
+
91
+ ### Docker
92
+
93
+ ```bash
94
+ docker run --rm \
95
+ -e AWS_ACCESS_KEY_ID \
96
+ -e AWS_SECRET_ACCESS_KEY \
97
+ -e AWS_SESSION_TOKEN \
98
+ -e AWS_DEFAULT_REGION=us-east-1 \
99
+ ghcr.io/ragnild/cloudleak scan aws region us-east-1
100
+
101
+ # Or mount your ~/.aws directory for SSO / named profiles
102
+ docker run --rm \
103
+ -v ~/.aws:/root/.aws:ro \
104
+ ghcr.io/ragnild/cloudleak scan aws region us-east-1 --profile my-profile
105
+ ```
106
+
107
+ ### Homebrew (macOS / Linux)
108
+
109
+ ```bash
110
+ brew tap ragnild/cloudleak
111
+ brew install cloudleak
112
+ ```
113
+
114
+ ### Install from source
115
+
116
+ ```bash
117
+ git clone https://github.com/ragnild/CloudLeak
118
+ cd CloudLeak
119
+ pip install -e ".[dev]"
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Quick start
125
+
126
+ ### Step 1 — Check permissions
127
+
128
+ ```bash
129
+ cloudleak doctor aws
130
+ ```
131
+
132
+ This runs a pre-flight check against your current credentials. It reports which permissions are available and which rules will be `SKIPPED` due to missing permissions.
133
+
134
+ ### Step 2 — Refresh the pricing cache
135
+
136
+ ```bash
137
+ cloudleak prices update
138
+ ```
139
+
140
+ Downloads on-demand pricing from the AWS Pricing API and writes `~/.cloudleak/prices.json`. Run this once, then again whenever you want fresh prices (the scanner warns you if the cache is older than 30 days).
141
+
142
+ ### Step 3 — Scan a region
143
+
144
+ ```bash
145
+ cloudleak scan aws region us-east-1
146
+ ```
147
+
148
+ That's it. cloudleak prints a colour-coded report to your terminal and exits.
149
+
150
+ ---
151
+
152
+ ## CLI Reference
153
+
154
+ ```
155
+ cloudleak scan aws region <region> Scan a single region
156
+ cloudleak scan aws --all-regions Scan all active regions
157
+ cloudleak scan aws region <region> --profile <name> Use a named AWS CLI profile
158
+ cloudleak scan aws region <region> --export json Export findings to JSON
159
+ cloudleak scan aws region <region> --export csv Export findings to CSV
160
+ cloudleak scan aws region <region> --export html Export findings to HTML report
161
+ cloudleak scan aws region <region> --export all All formats simultaneously
162
+ cloudleak scan aws region <region> --config ./cloudleak.yaml Custom config file
163
+ cloudleak scan aws --rules ec2,rds,s3 region <region> Scope to rule groups
164
+ cloudleak scan aws region <region> --severity HIGH Only HIGH findings
165
+ cloudleak doctor aws Pre-flight permission check
166
+ cloudleak prices update Refresh local pricing cache
167
+ cloudleak rules list List all rules
168
+ cloudleak rules describe <rule-id> Full spec for one rule
169
+ ```
170
+
171
+ ### `cloudleak scan aws`
172
+
173
+ | Flag | Default | Description |
174
+ |------|---------|-------------|
175
+ | `region <region>` | — | Scan a specific AWS region |
176
+ | `--all-regions` | false | Enumerate and scan all active regions |
177
+ | `--profile <name>` | (env/default) | Named AWS CLI profile |
178
+ | `--export <format>` | console | `json`, `csv`, `html`, `all` |
179
+ | `--output-file <path>` | stdout | Write export to file |
180
+ | `--config <path>` | (searched) | Path to `cloudleak.yaml` |
181
+ | `--rules <groups>` | all | Comma-separated rule groups: `ec2`, `ebs`, `networking`, `rds`, `s3`, `lambda`, `cloudwatch`, `aiml`, `containers`, `security`, `governance`, `analytics`, `messaging`, `desktop` |
182
+ | `--severity <level>` | all | Filter: `HIGH`, `MEDIUM`, `LOW` |
183
+ | `--verbose` | false | Show per-rule PASS/FOUND/SKIP detail |
184
+
185
+ ### `cloudleak doctor aws`
186
+
187
+ Checks every IAM permission in the manifest against your current credentials. Exits `0` if all available, `1` if any missing.
188
+
189
+ ### `cloudleak prices update`
190
+
191
+ Calls the AWS Bulk Pricing API (`us-east-1` endpoint) and writes `~/.cloudleak/prices.json`. Requires `pricing:GetProducts` permission. Does not need to run in a specific region.
192
+
193
+ ---
194
+
195
+ ## Configuration
196
+
197
+ cloudleak searches for `cloudleak.yaml` in: `./`, `~/.cloudleak/`, `/etc/cloudleak/`.
198
+
199
+ Copy `cloudleak.yaml.example` from the repo as your starting point — every key is documented inline.
200
+
201
+ ### Key sections
202
+
203
+ | Section | Controls |
204
+ |---------|----------|
205
+ | `scan.lookback_days` | CloudWatch metric lookback window (default: 14) |
206
+ | `scan.parallel_workers` | Max concurrent rule groups (default: 10) |
207
+ | `thresholds.ec2.*` | EC2 idle/stopped thresholds |
208
+ | `thresholds.ebs.*` | EBS unattached/snapshot/AMI age thresholds |
209
+ | `thresholds.networking.*` | LB/NAT/VPN/TGW idle thresholds |
210
+ | `thresholds.rds.*` | RDS/Aurora/ElastiCache idle thresholds |
211
+ | `thresholds.s3.*` | S3 request/lifecycle/EFS thresholds |
212
+ | `thresholds.lambda.*` | Lambda/SFN/SQS idle thresholds |
213
+ | `thresholds.cloudwatch.*` | Log group retention/idle/alarm thresholds |
214
+ | `thresholds.aiml.*` | SageMaker/Bedrock/Comprehend/Rekognition/Kendra/Lex thresholds |
215
+ | `thresholds.containers.*` | ECS/ECR/EKS/AppRunner thresholds |
216
+ | `thresholds.security.*` | Secrets Manager/KMS/IAM idle thresholds |
217
+ | `thresholds.governance.*` | Required tags list |
218
+ | `thresholds.analytics.*` | Redshift/OpenSearch/Glue/Kinesis/MSK/EMR idle thresholds |
219
+ | `thresholds.messaging.*` | Amazon MQ/Firehose idle thresholds |
220
+ | `thresholds.desktop.*` | WorkSpaces/AppStream idle thresholds |
221
+ | `pricing.cache_file` | Path to `prices.json` |
222
+ | `pricing.warn_if_stale_days` | Age after which a stale-cache warning is shown |
223
+
224
+ ---
225
+
226
+ ## IAM Setup
227
+
228
+ ### Attach the managed policy
229
+
230
+ The `iam/cloudleak-read-policy.json` file in this repo is a deployable IAM policy that grants every read permission cloudleak needs.
231
+
232
+ ```bash
233
+ # Create the policy in your account
234
+ aws iam create-policy \
235
+ --policy-name CloudLeakReadOnly \
236
+ --policy-document file://iam/cloudleak-read-policy.json
237
+
238
+ # Attach to a role (CI/CD)
239
+ aws iam attach-role-policy \
240
+ --role-name your-scanner-role \
241
+ --policy-arn arn:aws:iam::<account-id>:policy/CloudLeakReadOnly
242
+
243
+ # Or attach to a user
244
+ aws iam attach-user-policy \
245
+ --user-name your-scanner-user \
246
+ --policy-arn arn:aws:iam::<account-id>:policy/CloudLeakReadOnly
247
+ ```
248
+
249
+ ### Verify permissions
250
+
251
+ After attaching, run `cloudleak doctor aws` to confirm which permissions are active.
252
+
253
+ ### Note on pricing
254
+
255
+ The `cloudleak prices update` command additionally requires `pricing:GetProducts`. This is a separate permission from scan permissions. If you only want to run scans using a pre-built `prices.json`, you do not need to grant `pricing:GetProducts` to the scanner role.
256
+
257
+ ---
258
+
259
+ ## Output Formats
260
+
261
+ ### Console (default)
262
+
263
+ Colour-coded by severity, grouped by service. Includes a summary table at the end.
264
+
265
+ ```
266
+ ╔══════════════════════════════════════════════════════════════════╗
267
+ ║ cloudleak · Account: 123456789012 · Region: us-east-1 ║
268
+ ╚══════════════════════════════════════════════════════════════════╝
269
+
270
+ ── EC2 & Compute ──────────────────────────────────────────────────
271
+
272
+ 🔴 HIGH EC2-001 Stopped EC2 Instances
273
+ i-0abc123def456789 (m5.2xlarge) stopped 23 days
274
+ Attached EBS: 200 GB gp3 → est. waste: $16.00/mo
275
+ └─ Terminate or start. Create AMI first if needed.
276
+
277
+ ── SUMMARY ────────────────────────────────────────────────────────
278
+ Total findings: 34 Est. monthly waste: $1,847.23
279
+ HIGH: 18 MEDIUM: 12 LOW: 4 Skipped rules: 2
280
+ ```
281
+
282
+ ### JSON (`--export json`)
283
+
284
+ Structured output with scan metadata, summary, findings array, and skipped rules list. Schema defined in `PRODUCT_SPEC.md §7`.
285
+
286
+ ### CSV (`--export csv`)
287
+
288
+ Flat table, one row per finding. Columns: `rule_id, rule_name, severity, service, resource_id, resource_name, resource_arn, region, estimated_monthly_cost_usd, recommendation, evidence_json`.
289
+
290
+ ### HTML (`--export html`)
291
+
292
+ Self-contained single-file report. No external CDN. Includes summary cards, a bar chart of the top 10 most expensive findings, and a sortable/filterable table.
293
+
294
+ ---
295
+
296
+ ## All 114 Rules
297
+
298
+ ### EC2 & Compute
299
+
300
+ | Rule ID | Name | Severity |
301
+ |---------|------|----------|
302
+ | EC2-001 | Stopped EC2 Instances | HIGH |
303
+ | EC2-002 | Idle EC2 Instances (Low CPU) | HIGH |
304
+ | EC2-003 | Oversized EC2 Instances (Compute Optimizer) | MEDIUM |
305
+ | EC2-004 | On-Demand Candidates for Reserved Instances / Savings Plans | HIGH |
306
+ | EC2-012 | Unattached Elastic IPs | HIGH |
307
+ | EC2-013 | Detached Network Interfaces (ENIs) | MEDIUM |
308
+ | EC2-014 | Idle EC2 Dedicated Hosts | HIGH |
309
+ | EC2-015 | Burstable Instance Candidates (Wrong Family) | MEDIUM |
310
+ | EC2-016 | Unused EC2 Key Pairs | LOW |
311
+ | EC2-018 | Empty Placement Groups | LOW |
312
+ | EC2-019 | Unused EC2 Capacity Reservations | HIGH |
313
+
314
+ ### EBS & Block Storage
315
+
316
+ | Rule ID | Name | Severity |
317
+ |---------|------|----------|
318
+ | EC2-005 | Unattached EBS Volumes | HIGH |
319
+ | EC2-006 | Zero-Activity EBS Volumes | HIGH |
320
+ | EC2-007 | Oversized EBS Volumes (Compute Optimizer) | MEDIUM |
321
+ | EC2-008 | Old EBS Snapshots (No Linked AMI) | MEDIUM |
322
+ | EC2-009 | Snapshots from Terminated Instances | HIGH |
323
+ | EC2-010 | Old Unused AMIs | MEDIUM |
324
+ | EC2-011 | Invalid or Broken AMIs | LOW |
325
+ | EC2-017 | Oversized Root Volume vs Instance Needs | MEDIUM |
326
+ | EC2-020 | EBS gp2 Volumes Eligible for gp3 Migration | LOW |
327
+
328
+ ### Load Balancers & Networking
329
+
330
+ | Rule ID | Name | Severity |
331
+ |---------|------|----------|
332
+ | NET-001 | Idle Load Balancers | HIGH |
333
+ | NET-002 | Load Balancers with No Healthy Targets | HIGH |
334
+ | NET-003 | Empty Target Groups | MEDIUM |
335
+ | NET-004 | Idle NAT Gateways | HIGH |
336
+ | NET-005 | NAT Gateway with No Route Table References | HIGH |
337
+ | NET-006 | VPN Connections with Both Tunnels Down | MEDIUM |
338
+ | NET-007 | Idle Transit Gateway Attachments | MEDIUM |
339
+ | NET-008 | Idle VPC Interface Endpoints | MEDIUM |
340
+ | NET-009 | Unused Customer Gateways | LOW |
341
+ | NET-010 | Unused Internet Gateways | LOW |
342
+ | NET-011 | Idle Global Accelerators | MEDIUM |
343
+ | NET-012 | WAF WebACLs with No Associated Resources | MEDIUM |
344
+ | NET-013 | Idle CloudFront Distributions | MEDIUM |
345
+ | NET-015 | Direct Connect Virtual Interfaces in Down State | MEDIUM |
346
+
347
+ ### RDS & Databases
348
+
349
+ | Rule ID | Name | Severity |
350
+ |---------|------|----------|
351
+ | RDS-001 | Idle RDS Instances | HIGH |
352
+ | RDS-002 | Stopped RDS Instances | HIGH |
353
+ | RDS-003 | Oversized RDS Instances | MEDIUM |
354
+ | RDS-004 | Old Manual RDS Snapshots | MEDIUM |
355
+ | RDS-005 | RDS Instances Using gp2 Storage | MEDIUM |
356
+ | RDS-006 | Idle Aurora Clusters | HIGH |
357
+ | RDS-007 | Aurora Serverless v1 Without Auto-Pause (Idle) | HIGH |
358
+ | RDS-008 | Idle ElastiCache Clusters | HIGH |
359
+ | RDS-009 | Oversized ElastiCache Nodes | MEDIUM |
360
+ | RDS-010 | Idle DynamoDB Tables (Provisioned Mode) | MEDIUM |
361
+ | RDS-011 | DynamoDB Provisioned Capacity Under-Utilization | MEDIUM |
362
+ | RDS-012 | Old Manual Aurora Cluster Snapshots | MEDIUM |
363
+ | RDS-013 | Idle DocumentDB Clusters | HIGH |
364
+ | RDS-014 | Idle Neptune Clusters | HIGH |
365
+ | RDS-015 | Idle MemoryDB for Redis Clusters | HIGH |
366
+ | RDS-016 | Idle RDS Proxy Endpoints | MEDIUM |
367
+
368
+ ### S3 & Storage
369
+
370
+ | Rule ID | Name | Severity |
371
+ |---------|------|----------|
372
+ | S3-001 | S3 Buckets with No Requests | MEDIUM |
373
+ | S3-002 | S3 Buckets with No Lifecycle Policy | LOW |
374
+ | S3-003 | S3 Buckets with No Storage Tiering Transition | LOW |
375
+ | S3-004 | S3 Versioning Enabled Without Version Expiry Rule | LOW |
376
+ | S3-005 | S3 Intelligent-Tiering on Predictably-Accessed Buckets | LOW |
377
+ | S3-006 | Requester Pays Disabled on Cross-Account Shared Buckets | LOW |
378
+ | S3-007 | Empty Glacier Vaults | LOW |
379
+ | S3-008 | Idle EFS File Systems | HIGH |
380
+ | S3-009 | EFS Over-Provisioned Throughput | MEDIUM |
381
+ | S3-010 | Idle FSx File Systems | HIGH |
382
+
383
+ ### Lambda & Serverless
384
+
385
+ | Rule ID | Name | Severity |
386
+ |---------|------|----------|
387
+ | LAM-001 | Lambda Functions with No Invocations | MEDIUM |
388
+ | LAM-002 | Lambda Provisioned Concurrency Underutilized | HIGH |
389
+ | LAM-003 | Lambda Functions with Over-Allocated Memory | MEDIUM |
390
+ | LAM-004 | API Gateway APIs with No Requests | MEDIUM |
391
+ | LAM-005 | API Gateway REST APIs with No Backend Integrations | MEDIUM |
392
+ | LAM-006 | Idle Step Functions State Machines | LOW |
393
+ | LAM-007 | EventBridge Rules with No Targets | LOW |
394
+ | LAM-008 | SQS Queues with No Activity | LOW |
395
+ | LAM-009 | Lambda Function URLs with No Traffic | LOW |
396
+ | LAM-010 | Idle API Gateway WebSocket APIs | MEDIUM |
397
+
398
+ ### CloudWatch & Logging
399
+
400
+ | Rule ID | Name | Severity |
401
+ |---------|------|----------|
402
+ | CW-001 | CloudWatch Log Groups Without Retention Policy | LOW |
403
+ | CW-002 | CloudWatch Log Groups with Zero Ingestion | LOW |
404
+ | CW-003 | CloudWatch Alarms Stuck in INSUFFICIENT_DATA | LOW |
405
+ | CW-004 | DynamoDB Contributor Insights Enabled on Idle Tables | MEDIUM |
406
+ | CW-005 | X-Ray Sampling Rules for Services with No Traces | LOW |
407
+ | CW-006 | CloudWatch Metric Streams with No Active Consumers | MEDIUM |
408
+
409
+ ### AI/ML Services
410
+
411
+ | Rule ID | Name | Severity |
412
+ |---------|------|----------|
413
+ | ML-001 | SageMaker Endpoints with No Invocations | HIGH |
414
+ | ML-002 | SageMaker Notebook Instances Stopped Too Long | MEDIUM |
415
+ | ML-003 | SageMaker Studio Apps Running Idle | MEDIUM |
416
+ | ML-004 | Stuck SageMaker Training Jobs | HIGH |
417
+ | ML-005 | Bedrock Provisioned Throughput with No Invocations | HIGH |
418
+ | ML-006 | Amazon Comprehend Endpoints with No Requests | HIGH |
419
+ | ML-007 | Rekognition Custom Labels Models Running Idle | HIGH |
420
+ | ML-008 | SageMaker Model Packages Unused After 90 Days | LOW |
421
+ | ML-009 | Kendra Indexes with No Query Activity | HIGH |
422
+ | ML-010 | Lex V2 Bot Aliases with No Conversations | MEDIUM |
423
+
424
+ ### Containers
425
+
426
+ | Rule ID | Name | Severity |
427
+ |---------|------|----------|
428
+ | CON-001 | ECS Services with Zero Running Tasks | HIGH |
429
+ | CON-002 | ECR Repositories with No Recent Image Pulls | LOW |
430
+ | CON-003 | Old ECR Images Never Pulled | MEDIUM |
431
+ | CON-004 | EKS Clusters with No Active Nodes | HIGH |
432
+ | CON-005 | ECS Fargate Services with Low CPU Utilization | MEDIUM |
433
+ | CON-006 | App Runner Services with No Requests | HIGH |
434
+
435
+ ### Security & Identity
436
+
437
+ | Rule ID | Name | Severity |
438
+ |---------|------|----------|
439
+ | SEC-001 | Shield Advanced with No Protected Resources | HIGH |
440
+ | SEC-002 | Unused ACM Certificates | LOW |
441
+ | SEC-003 | Secrets Manager Secrets Not Accessed | MEDIUM |
442
+ | SEC-004 | Unused KMS Customer Managed Keys | MEDIUM |
443
+ | SEC-005 | IAM Roles with No Recent Activity | MEDIUM |
444
+ | SEC-007 | Macie Enabled with No Classification Jobs | MEDIUM |
445
+
446
+ ### Tagging & Governance
447
+
448
+ | Rule ID | Name | Severity |
449
+ |---------|------|----------|
450
+ | GOV-001 | Untagged Critical Resources | MEDIUM |
451
+ | GOV-002 | CloudFormation Stacks in ROLLBACK_COMPLETE | HIGH |
452
+ | GOV-003 | Service Catalog Tainted Provisioned Products | MEDIUM |
453
+ | GOV-004 | RDS Instances Without Cost Allocation Tags | LOW |
454
+
455
+ ### Analytics & Data
456
+
457
+ | Rule ID | Name | Severity |
458
+ |---------|------|----------|
459
+ | ANA-001 | Idle Redshift Clusters | HIGH |
460
+ | ANA-002 | Idle OpenSearch / Elasticsearch Domains | HIGH |
461
+ | ANA-003 | Glue Development Endpoints Running Idle | HIGH |
462
+ | ANA-004 | Idle Kinesis Data Streams | MEDIUM |
463
+ | ANA-005 | Idle MSK (Managed Kafka) Clusters | HIGH |
464
+ | ANA-006 | EMR Clusters with No Active Steps | HIGH |
465
+ | ANA-007 | Glue ETL Jobs Scheduled but Never Successfully Executed | MEDIUM |
466
+ | ANA-008 | Redshift Serverless Namespaces with No RPU Activity | MEDIUM |
467
+
468
+ ### Messaging & Streaming
469
+
470
+ | Rule ID | Name | Severity |
471
+ |---------|------|----------|
472
+ | MSG-001 | Idle Amazon MQ Brokers | HIGH |
473
+ | MSG-002 | Firehose Delivery Streams with No Records | MEDIUM |
474
+
475
+ ### Desktop & Streaming
476
+
477
+ Severity varies by resource configuration: `HIGH` for always-on resources, `LOW` for auto-stop or on-demand resources.
478
+
479
+ | Rule ID | Name | Severity |
480
+ |---------|------|----------|
481
+ | DSK-001 | Amazon WorkSpaces Not Used | HIGH / LOW |
482
+ | DSK-002 | AppStream 2.0 Fleets with No Active Sessions | HIGH / LOW |
483
+
484
+ ---
485
+
486
+ ## Contributing
487
+
488
+ ### Adding a rule
489
+
490
+ 1. Create a file in `src/rules/<group>.py` (or add to an existing group file).
491
+ 2. Implement the `Rule` protocol: `rule_id`, `name`, `service`, `group`, `severity`, `description`, `rationale`, `permissions`, `thresholds`, and `check(ctx) -> list[Finding]`.
492
+ 3. Call `register(_YourRuleClass())` at the bottom of the rule module — the rule self-registers when the module is imported.
493
+ 4. Add a decision contract doc to `docs/rules/`.
494
+ 5. Write a unit test in `tests/unit/` with a mocked AWS response fixture.
495
+ 6. Run `cloudleak rules list` to confirm it appears.
496
+
497
+ ### Development setup
498
+
499
+ ```bash
500
+ git clone https://github.com/ragnild/cloudleak
501
+ cd cloudleak
502
+ pip install -e ".[dev]"
503
+ pytest tests/unit/
504
+ ```
505
+
506
+ ### Principles — always enforce before merging
507
+
508
+ - No write API calls (`Create`, `Delete`, `Update`, `Put`, `Stop`, `Modify`)
509
+ - No hardcoded thresholds — always read from config with a default
510
+ - `AccessDenied` → `SKIPPED`, never a crash
511
+ - No live pricing calls during a scan
512
+ - Every `Finding` has `resource_id`, `resource_arn`, `evidence`, `estimated_monthly_cost_usd`, `recommendation`
513
+
514
+ ---
515
+
516
+ ## License
517
+
518
+ MIT — see [LICENSE](LICENSE).