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.
- cloudleak-1.0.0/LICENSE +21 -0
- cloudleak-1.0.0/PKG-INFO +518 -0
- cloudleak-1.0.0/README.md +494 -0
- cloudleak-1.0.0/cloudleak.egg-info/PKG-INFO +518 -0
- cloudleak-1.0.0/cloudleak.egg-info/SOURCES.txt +63 -0
- cloudleak-1.0.0/cloudleak.egg-info/dependency_links.txt +1 -0
- cloudleak-1.0.0/cloudleak.egg-info/entry_points.txt +2 -0
- cloudleak-1.0.0/cloudleak.egg-info/requires.txt +15 -0
- cloudleak-1.0.0/cloudleak.egg-info/top_level.txt +1 -0
- cloudleak-1.0.0/pyproject.toml +62 -0
- cloudleak-1.0.0/setup.cfg +4 -0
- cloudleak-1.0.0/src/__init__.py +6 -0
- cloudleak-1.0.0/src/auth/__init__.py +0 -0
- cloudleak-1.0.0/src/auth/session.py +53 -0
- cloudleak-1.0.0/src/cli/__init__.py +531 -0
- cloudleak-1.0.0/src/config/__init__.py +0 -0
- cloudleak-1.0.0/src/config/loader.py +187 -0
- cloudleak-1.0.0/src/output/__init__.py +8 -0
- cloudleak-1.0.0/src/output/console.py +266 -0
- cloudleak-1.0.0/src/output/csv_.py +44 -0
- cloudleak-1.0.0/src/output/executive.py +26 -0
- cloudleak-1.0.0/src/output/executive_model.py +319 -0
- cloudleak-1.0.0/src/output/html_.py +921 -0
- cloudleak-1.0.0/src/output/json_.py +76 -0
- cloudleak-1.0.0/src/output/templates/executive.html.j2 +506 -0
- cloudleak-1.0.0/src/pricing/__init__.py +0 -0
- cloudleak-1.0.0/src/pricing/cache.py +172 -0
- cloudleak-1.0.0/src/pricing/prices.json +4 -0
- cloudleak-1.0.0/src/pricing/update.py +381 -0
- cloudleak-1.0.0/src/rules/__init__.py +90 -0
- cloudleak-1.0.0/src/rules/aiml.py +1019 -0
- cloudleak-1.0.0/src/rules/analytics.py +939 -0
- cloudleak-1.0.0/src/rules/appsync.py +140 -0
- cloudleak-1.0.0/src/rules/batch.py +154 -0
- cloudleak-1.0.0/src/rules/cloudwatch.py +586 -0
- cloudleak-1.0.0/src/rules/codebuild.py +139 -0
- cloudleak-1.0.0/src/rules/codepipeline.py +135 -0
- cloudleak-1.0.0/src/rules/connect.py +123 -0
- cloudleak-1.0.0/src/rules/containers.py +754 -0
- cloudleak-1.0.0/src/rules/datasync.py +109 -0
- cloudleak-1.0.0/src/rules/desktop.py +318 -0
- cloudleak-1.0.0/src/rules/ebs.py +866 -0
- cloudleak-1.0.0/src/rules/ec2.py +1088 -0
- cloudleak-1.0.0/src/rules/gamelift.py +239 -0
- cloudleak-1.0.0/src/rules/governance.py +443 -0
- cloudleak-1.0.0/src/rules/grafana.py +119 -0
- cloudleak-1.0.0/src/rules/ivs.py +116 -0
- cloudleak-1.0.0/src/rules/lambda_.py +997 -0
- cloudleak-1.0.0/src/rules/lightsail.py +142 -0
- cloudleak-1.0.0/src/rules/messaging.py +420 -0
- cloudleak-1.0.0/src/rules/networking.py +1378 -0
- cloudleak-1.0.0/src/rules/prometheus.py +116 -0
- cloudleak-1.0.0/src/rules/rds.py +1542 -0
- cloudleak-1.0.0/src/rules/s3.py +905 -0
- cloudleak-1.0.0/src/rules/security.py +753 -0
- cloudleak-1.0.0/src/rules/transcoder.py +132 -0
- cloudleak-1.0.0/src/rules/transfer.py +123 -0
- cloudleak-1.0.0/src/scanner/__init__.py +0 -0
- cloudleak-1.0.0/src/scanner/cloudwatch.py +154 -0
- cloudleak-1.0.0/src/scanner/compute_optimizer.py +82 -0
- cloudleak-1.0.0/src/scanner/context.py +21 -0
- cloudleak-1.0.0/src/scanner/doctor.py +378 -0
- cloudleak-1.0.0/src/scanner/finding.py +70 -0
- cloudleak-1.0.0/src/scanner/orchestrator.py +167 -0
- cloudleak-1.0.0/src/scanner/rule.py +23 -0
cloudleak-1.0.0/LICENSE
ADDED
|
@@ -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.
|
cloudleak-1.0.0/PKG-INFO
ADDED
|
@@ -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)
|
|
30
|
+
[](https://pypi.org/project/cloudleak/)
|
|
31
|
+
[](https://github.com/ragnild/CloudLeak/actions/workflows/ci.yml)
|
|
32
|
+
[](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).
|