sudiviz 0.3.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.
- sudiviz-0.3.0/LICENSE +21 -0
- sudiviz-0.3.0/MANIFEST.in +10 -0
- sudiviz-0.3.0/PKG-INFO +543 -0
- sudiviz-0.3.0/README.md +489 -0
- sudiviz-0.3.0/pyproject.toml +96 -0
- sudiviz-0.3.0/setup.cfg +4 -0
- sudiviz-0.3.0/sudiviz/__init__.py +9 -0
- sudiviz-0.3.0/sudiviz/__main__.py +5 -0
- sudiviz-0.3.0/sudiviz/cli.py +598 -0
- sudiviz-0.3.0/sudiviz/discovery/__init__.py +26 -0
- sudiviz-0.3.0/sudiviz/discovery/aws.py +873 -0
- sudiviz-0.3.0/sudiviz/discovery/models.py +337 -0
- sudiviz-0.3.0/sudiviz/discovery/terraform.py +257 -0
- sudiviz-0.3.0/sudiviz/graph/__init__.py +24 -0
- sudiviz-0.3.0/sudiviz/graph/analyzer.py +512 -0
- sudiviz-0.3.0/sudiviz/graph/builder.py +315 -0
- sudiviz-0.3.0/sudiviz/graph/visualizer.py +356 -0
- sudiviz-0.3.0/sudiviz/remediation/__init__.py +4 -0
- sudiviz-0.3.0/sudiviz/remediation/engine.py +385 -0
- sudiviz-0.3.0/sudiviz/tui.py +289 -0
- sudiviz-0.3.0/sudiviz/utils/__init__.py +1 -0
- sudiviz-0.3.0/sudiviz/utils/auth.py +146 -0
- sudiviz-0.3.0/sudiviz/utils/branding.py +49 -0
- sudiviz-0.3.0/sudiviz/utils/reachability.py +77 -0
- sudiviz-0.3.0/sudiviz/web.py +205 -0
- sudiviz-0.3.0/sudiviz/web_templates/cytoscape.js +106 -0
- sudiviz-0.3.0/sudiviz/web_templates/index.html +390 -0
- sudiviz-0.3.0/sudiviz/web_templates/style.css +90 -0
- sudiviz-0.3.0/sudiviz.egg-info/PKG-INFO +543 -0
- sudiviz-0.3.0/sudiviz.egg-info/SOURCES.txt +34 -0
- sudiviz-0.3.0/sudiviz.egg-info/dependency_links.txt +1 -0
- sudiviz-0.3.0/sudiviz.egg-info/entry_points.txt +2 -0
- sudiviz-0.3.0/sudiviz.egg-info/requires.txt +37 -0
- sudiviz-0.3.0/sudiviz.egg-info/top_level.txt +1 -0
- sudiviz-0.3.0/tests/test_analyzer.py +234 -0
- sudiviz-0.3.0/tests/test_discovery.py +215 -0
sudiviz-0.3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sudipto Ghosh
|
|
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
|
|
13
|
+
all 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
|
|
21
|
+
THE SOFTWARE.
|
sudiviz-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sudiviz
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Sufficient visibility into cloud infrastructure failures — live AWS topology + Terraform drift in one CLI.
|
|
5
|
+
Author: Sudipto Ghosh
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/sudiptoghosh/sudiviz
|
|
8
|
+
Project-URL: Issues, https://github.com/sudiptoghosh/sudiviz/issues
|
|
9
|
+
Keywords: aws,terraform,observability,networking,diagnose,alb,vpc
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: System Administrators
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: System :: Networking :: Monitoring
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: typer>=0.15.0
|
|
23
|
+
Requires-Dist: boto3>=1.35.0
|
|
24
|
+
Requires-Dist: botocore>=1.35.0
|
|
25
|
+
Requires-Dist: networkx>=3.0
|
|
26
|
+
Requires-Dist: pydantic>=2.0
|
|
27
|
+
Requires-Dist: rich>=13.0
|
|
28
|
+
Requires-Dist: aiohttp>=3.9
|
|
29
|
+
Requires-Dist: graphviz>=0.20
|
|
30
|
+
Requires-Dist: jinja2>=3.1
|
|
31
|
+
Provides-Extra: tui
|
|
32
|
+
Requires-Dist: textual>=0.50.0; extra == "tui"
|
|
33
|
+
Provides-Extra: web
|
|
34
|
+
Requires-Dist: fastapi>=0.115.0; extra == "web"
|
|
35
|
+
Requires-Dist: uvicorn>=0.30.0; extra == "web"
|
|
36
|
+
Requires-Dist: websockets>=13.0; extra == "web"
|
|
37
|
+
Provides-Extra: diagrams
|
|
38
|
+
Requires-Dist: diagrams>=0.23; extra == "diagrams"
|
|
39
|
+
Provides-Extra: terraform
|
|
40
|
+
Requires-Dist: python-terraform>=0.10; extra == "terraform"
|
|
41
|
+
Provides-Extra: all
|
|
42
|
+
Requires-Dist: textual>=0.50.0; extra == "all"
|
|
43
|
+
Requires-Dist: fastapi>=0.115.0; extra == "all"
|
|
44
|
+
Requires-Dist: uvicorn>=0.30.0; extra == "all"
|
|
45
|
+
Requires-Dist: websockets>=13.0; extra == "all"
|
|
46
|
+
Requires-Dist: diagrams>=0.23; extra == "all"
|
|
47
|
+
Requires-Dist: python-terraform>=0.10; extra == "all"
|
|
48
|
+
Provides-Extra: dev
|
|
49
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
50
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
51
|
+
Requires-Dist: ruff>=0.5; extra == "dev"
|
|
52
|
+
Requires-Dist: mypy>=1.8; extra == "dev"
|
|
53
|
+
Dynamic: license-file
|
|
54
|
+
|
|
55
|
+
# sudiviz
|
|
56
|
+
|
|
57
|
+
> **sudiviz** = **sudipto's** + **viz** → *X-ray vision for your cloud infrastructure.*
|
|
58
|
+
|
|
59
|
+
Live, interactive topology + diagnosis for AWS (Azure/GCP next). When a
|
|
60
|
+
deployment 503s, sudiviz tells you **why** — in plain English — and **draws
|
|
61
|
+
you a picture**. Every render is a fresh API call. Every orphan pulses red.
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
_ _ _
|
|
65
|
+
___ _ _ __| (_)_ _(_)____
|
|
66
|
+
/ __| | | |/ _` | \ \ / / |_ /
|
|
67
|
+
\__ \ |_| | (_| | |\ V /| |/ /
|
|
68
|
+
|___/\__,_|\__,_|_| \_/ |_/___|
|
|
69
|
+
|
|
70
|
+
X-ray vision for your cloud infrastructure
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Why sudiviz?
|
|
76
|
+
|
|
77
|
+
Hava.io and Cloudcraft.co generate gorgeous diagrams — **but they're snapshots**.
|
|
78
|
+
By the time you reload, your problem has moved. sudiviz is built around live
|
|
79
|
+
data: every render is a fresh API call, every node is clickable, every orphan
|
|
80
|
+
is highlighted in red dashed lines.
|
|
81
|
+
|
|
82
|
+
| Feature | sudiviz | Hava.io | Cloudcraft |
|
|
83
|
+
|--------------------------------------|:--------:|:--------------:|:------------:|
|
|
84
|
+
| Live data (no manual refresh) | ✅ | ❌ (static) | ❌ (static) |
|
|
85
|
+
| Terminal UI (Textual) | ✅ | ❌ | ❌ |
|
|
86
|
+
| Interactive web (Cytoscape.js) | ✅ | ✅ | ✅ |
|
|
87
|
+
| WebSocket real-time updates | ✅ | ❌ | ❌ |
|
|
88
|
+
| PNG export | ✅ | ✅ | ✅ |
|
|
89
|
+
| Plain-English fix suggestions | ✅ | ❌ | ❌ |
|
|
90
|
+
| Terraform drift detection | ✅ | ❌ | ❌ |
|
|
91
|
+
| **Orphan detection** (red dashed) | ✅ | ❌ | ❌ |
|
|
92
|
+
| ECS / EKS / RDS / Lambda / S3 | ✅ | ✅ | ✅ |
|
|
93
|
+
| Security & encryption checks | ✅ | ❌ | ❌ |
|
|
94
|
+
| Free / open source | ✅ MIT | ❌ ($29/mo) | ❌ ($49/mo) |
|
|
95
|
+
| CI-friendly `--json` flag | ✅ | ❌ | ❌ |
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Install
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install sudiviz # core CLI (EC2, ALB, SGs, basic discovery)
|
|
103
|
+
pip install 'sudiviz[all]' # + TUI, web server, PNG diagrams
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
> **Auth:** sudiviz uses the standard boto3 credential chain — env vars,
|
|
107
|
+
> `~/.aws/credentials`, SSO, instance profile. Credentials are **never**
|
|
108
|
+
> accepted as CLI flags. Run `aws configure` or set `AWS_ACCESS_KEY_ID` /
|
|
109
|
+
> `AWS_SECRET_ACCESS_KEY` / `AWS_DEFAULT_REGION` before running.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## AWS services discovered
|
|
114
|
+
|
|
115
|
+
sudiviz discovers these services in parallel from your live AWS account:
|
|
116
|
+
|
|
117
|
+
| Service | What's collected |
|
|
118
|
+
|---------|-----------------|
|
|
119
|
+
| **ALB / NLB** | Load balancers, listeners, listener rules, scheme, state |
|
|
120
|
+
| **Target Groups** | Protocol, port, per-target health (healthy / unhealthy / draining) |
|
|
121
|
+
| **EC2 Instances** | State, IPs, subnet, security group memberships |
|
|
122
|
+
| **Security Groups** | Ingress/egress rules, ENI attachments |
|
|
123
|
+
| **ECS** | Clusters → Services (desired vs running tasks, launch type, TG links) |
|
|
124
|
+
| **EKS** | Clusters → Node Groups (status, capacity type, scaling config) |
|
|
125
|
+
| **RDS** | DB instances (engine, status, endpoint, encryption, public access) |
|
|
126
|
+
| **Lambda** | Functions (runtime, state, VPC config, event source mappings) |
|
|
127
|
+
| **S3** | Buckets (versioning, public access block, server-side encryption) |
|
|
128
|
+
| **VPC** | Used as the graph root when `--vpc-id` is supplied |
|
|
129
|
+
|
|
130
|
+
All discovery calls run via `asyncio.to_thread` — a typical account with
|
|
131
|
+
~50 resources finishes in under 5 seconds.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Three visualization modes
|
|
136
|
+
|
|
137
|
+
### 1. Terminal (default)
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
sudiviz diagnose --region us-east-1
|
|
141
|
+
sudiviz diagnose --vpc-id vpc-abc --service-tag Service=checkout
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
╭─ sudiviz topology ──────────────────────────────────────╮
|
|
146
|
+
│ Topology │
|
|
147
|
+
│ ├── alb: web-prod │
|
|
148
|
+
│ │ └── ──▶ target_group: web-prod-tg [2/3] │
|
|
149
|
+
│ │ ├── ──▶ instance: i-0a1b2c (healthy) │
|
|
150
|
+
│ │ └── ──▶ instance: i-0a1b2d (unhealthy) │
|
|
151
|
+
│ ├── ECS │
|
|
152
|
+
│ │ └── ecs_cluster: prod-cluster │
|
|
153
|
+
│ │ └── ──▶ ecs_service: api [3/3 running] │
|
|
154
|
+
│ ├── EKS │
|
|
155
|
+
│ │ └── eks_cluster: prod ──▶ eks_nodegroup: workers │
|
|
156
|
+
│ ├── RDS │
|
|
157
|
+
│ │ └── rds: mydb (postgres / available) │
|
|
158
|
+
│ ├── Lambda │
|
|
159
|
+
│ │ └── lambda: worker (python3.12 / Active) │
|
|
160
|
+
│ ├── S3 │
|
|
161
|
+
│ │ └── s3: my-bucket │
|
|
162
|
+
│ └── ORPHANS │
|
|
163
|
+
│ ╌╌ target_group: legacy-tg │
|
|
164
|
+
│ ╌╌ security_group: unused-sg │
|
|
165
|
+
╰─────────────────────────────────────────────────────────╯
|
|
166
|
+
|
|
167
|
+
┌──────────┬─────────────────────────────────────┬──────────────────────────────────────┐
|
|
168
|
+
│ Severity │ Title │ Detail │
|
|
169
|
+
├──────────┼─────────────────────────────────────┼──────────────────────────────────────┤
|
|
170
|
+
│ critical │ S3 'my-bucket': public access open │ Enable S3 Block Public Access… │
|
|
171
|
+
│ critical │ TG 'web-prod-tg': 2/3 healthy │ 1 target failing health checks… │
|
|
172
|
+
│ warning │ RDS 'mydb': storage not encrypted │ Enable SSE-S3 or SSE-KMS… │
|
|
173
|
+
│ warning │ Orphan target group: legacy-tg │ No listener forwards here… │
|
|
174
|
+
│ info │ Unused security group: unused-sg │ Safe to delete. │
|
|
175
|
+
└──────────┴─────────────────────────────────────┴──────────────────────────────────────┘
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### 2. Textual TUI (mouse + keyboard)
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
sudiviz tui --vpc-id vpc-abc
|
|
182
|
+
pip install 'sudiviz[tui]' # if not already installed
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
| Key | Action |
|
|
186
|
+
|-----|---------------------------------|
|
|
187
|
+
| `r` | Refresh discovery |
|
|
188
|
+
| `o` | Toggle orphan-only filter |
|
|
189
|
+
| `d` | Drift overlay hint |
|
|
190
|
+
| `q` | Quit |
|
|
191
|
+
|
|
192
|
+
Click any row to populate the details pane — shows ARN, health, engine,
|
|
193
|
+
task counts, encryption status, and more depending on node type.
|
|
194
|
+
|
|
195
|
+
Status bar shows live counts for all services:
|
|
196
|
+
```
|
|
197
|
+
● 123456789 us-east-1 vpc=all · 3 ALBs · 5 TGs · 8 EC2 · 2 ECS clusters (6 svcs) · 1 EKS clusters · 3 RDS · 4 Lambda · 12 S3 · refreshed 14:23:01
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### 3. Interactive web (Cytoscape.js)
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
pip install 'sudiviz[web]' # if not already installed
|
|
204
|
+
sudiviz graph --output web --port 8000 --open
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Opens a browser with a live topology graph that:
|
|
208
|
+
|
|
209
|
+
- Pans, zooms, and drags nodes freely
|
|
210
|
+
- Click any node → sidebar shows full metadata (ARN, health, engine, task counts, encryption)
|
|
211
|
+
- **Cmd/Ctrl-click** opens the AWS Console directly for that resource
|
|
212
|
+
- Auto-refreshes every 30 s via WebSocket (toggleable)
|
|
213
|
+
- **Orphan edges pulse red dashed** — impossible to miss
|
|
214
|
+
- **⚠ Orphans button** filters the graph to only show problem nodes
|
|
215
|
+
- **⤓ PNG button** exports the current view as a PNG
|
|
216
|
+
|
|
217
|
+
**Node colours by kind:**
|
|
218
|
+
|
|
219
|
+
| Node type | Shape | Colour |
|
|
220
|
+
|-----------|-------|--------|
|
|
221
|
+
| ALB / NLB | Cut rectangle | Blue |
|
|
222
|
+
| Target Group | Rounded rect | Cyan |
|
|
223
|
+
| EC2 Instance | Rounded rect | Purple |
|
|
224
|
+
| Security Group | Diamond | Amber |
|
|
225
|
+
| ECS Cluster | Barrel | Pink |
|
|
226
|
+
| ECS Service | Rounded rect | Fuchsia |
|
|
227
|
+
| EKS Cluster | Hexagon | Blue |
|
|
228
|
+
| EKS Node Group | Rounded rect | Sky |
|
|
229
|
+
| RDS | Barrel | Yellow |
|
|
230
|
+
| Lambda | Triangle | Green |
|
|
231
|
+
| S3 | Rounded rect | Orange |
|
|
232
|
+
| VPC | Rectangle | Gray |
|
|
233
|
+
|
|
234
|
+
Or export a static PNG:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
sudiviz graph --output png --file topology.png --open
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Connectivity indicators — green / red / dashed
|
|
243
|
+
|
|
244
|
+
sudiviz uses a consistent visual language across **all three output modes**:
|
|
245
|
+
|
|
246
|
+
| State | Terminal | Web (Cytoscape) | PNG (Graphviz) |
|
|
247
|
+
|-------|----------|-----------------|----------------|
|
|
248
|
+
| Healthy edge | `──▶` (dim) | Solid green line (`#22c55e`) | `style=solid color=#374151` |
|
|
249
|
+
| Orphan edge | `╌╌▶` (bold red) | Dashed red line (`#dc2626`) + pulse | `style=dashed color=#dc2626 penwidth=2` |
|
|
250
|
+
| Healthy node border | — | Green border | Green fill `#dcfce7` |
|
|
251
|
+
| Unhealthy node border | — | Red border | Red fill `#fecaca` |
|
|
252
|
+
| Orphan node | Red dashed section | Red dashed border + red fill `#fee2e2` | Red fill `#fee2e2` |
|
|
253
|
+
|
|
254
|
+
### What triggers a red dashed line?
|
|
255
|
+
|
|
256
|
+
An edge turns red and dashed whenever **either endpoint** is an orphan:
|
|
257
|
+
|
|
258
|
+
1. **Orphan target group** — no ALB listener has a `forwards_to` edge pointing at it.
|
|
259
|
+
2. **Orphan instance** — not registered in any target group.
|
|
260
|
+
3. **Orphan security group** — no ENI or resource has a `guarded_by` edge to it.
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
sudiviz diagnose --show-unattached --highlight-orphans
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
Algorithm lives in [sudiviz/graph/analyzer.py](sudiviz/graph/analyzer.py) →
|
|
267
|
+
`mark_orphaned_edges()`. It annotates `node['orphan']=True` and
|
|
268
|
+
`edge['style']='dashed'` so all visualizers stay output-agnostic.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Diagnostic rules — what sudiviz checks
|
|
273
|
+
|
|
274
|
+
### Load balancer + networking
|
|
275
|
+
| Check | Severity |
|
|
276
|
+
|-------|----------|
|
|
277
|
+
| Target group has unhealthy targets | critical / warning |
|
|
278
|
+
| Instance SG missing required port from ALB SG | critical |
|
|
279
|
+
| Orphan target group (no listener routes to it) | warning |
|
|
280
|
+
| Instance not in any target group | info |
|
|
281
|
+
| Security group attached to nothing | info |
|
|
282
|
+
|
|
283
|
+
### ECS
|
|
284
|
+
| Check | Severity |
|
|
285
|
+
|-------|----------|
|
|
286
|
+
| Service `running < desired` tasks | critical (0 running) / warning |
|
|
287
|
+
| Service has 0 desired tasks | — (skipped, intentional scale-down) |
|
|
288
|
+
|
|
289
|
+
### EKS
|
|
290
|
+
| Check | Severity |
|
|
291
|
+
|-------|----------|
|
|
292
|
+
| Cluster not in ACTIVE state | critical |
|
|
293
|
+
| Node group not in ACTIVE state | warning |
|
|
294
|
+
|
|
295
|
+
### RDS
|
|
296
|
+
| Check | Severity |
|
|
297
|
+
|-------|----------|
|
|
298
|
+
| Instance not `available` | critical (failed) / warning |
|
|
299
|
+
| Storage encryption disabled | warning |
|
|
300
|
+
| Publicly accessible | warning |
|
|
301
|
+
|
|
302
|
+
### Lambda
|
|
303
|
+
| Check | Severity |
|
|
304
|
+
|-------|----------|
|
|
305
|
+
| Function state not `Active` | warning |
|
|
306
|
+
|
|
307
|
+
### S3
|
|
308
|
+
| Check | Severity |
|
|
309
|
+
|-------|----------|
|
|
310
|
+
| Public access not fully blocked | **critical** |
|
|
311
|
+
| Server-side encryption not enabled | warning |
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## Terraform drift detection
|
|
316
|
+
|
|
317
|
+
```bash
|
|
318
|
+
terraform show -json > tfstate.json
|
|
319
|
+
sudiviz drift --tfstate tfstate.json --region us-east-1
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
Compares your Terraform state against live AWS. Covers:
|
|
323
|
+
|
|
324
|
+
| Terraform resource type | Live check |
|
|
325
|
+
|------------------------|------------|
|
|
326
|
+
| `aws_lb` / `aws_alb` | Load balancers |
|
|
327
|
+
| `aws_lb_target_group` | Target groups |
|
|
328
|
+
| `aws_security_group` | Security groups |
|
|
329
|
+
| `aws_instance` | EC2 instances |
|
|
330
|
+
| `aws_ecs_cluster` | ECS clusters |
|
|
331
|
+
| `aws_ecs_service` | ECS services |
|
|
332
|
+
| `aws_eks_cluster` | EKS clusters |
|
|
333
|
+
| `aws_db_instance` | RDS instances |
|
|
334
|
+
| `aws_lambda_function` | Lambda functions |
|
|
335
|
+
|
|
336
|
+
Drift kinds reported:
|
|
337
|
+
|
|
338
|
+
| Kind | Meaning |
|
|
339
|
+
|------|---------|
|
|
340
|
+
| `missing` | Terraform expects this, AWS doesn't have it |
|
|
341
|
+
| `orphan_in_aws` | AWS has it, Terraform doesn't (manual change) |
|
|
342
|
+
| `orphan_listener` | TF expected a listener TG, but no live listener routes there |
|
|
343
|
+
|
|
344
|
+
Exits non-zero on drift — use as a CI gate:
|
|
345
|
+
|
|
346
|
+
```yaml
|
|
347
|
+
- run: sudiviz drift --tfstate plan.json --json > drift.json
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
---
|
|
351
|
+
|
|
352
|
+
## CI / scripting (`--json`)
|
|
353
|
+
|
|
354
|
+
Every command emits machine-readable JSON with `--json`:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
# Fail CI if any critical issue exists
|
|
358
|
+
sudiviz diagnose --region us-east-1 --json | jq '.diagnosis.fixes[] | select(.severity=="critical")'
|
|
359
|
+
|
|
360
|
+
# Drift as a CI gate
|
|
361
|
+
sudiviz drift --tfstate tfstate.json --json
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Exit codes:
|
|
365
|
+
|
|
366
|
+
| Code | Meaning |
|
|
367
|
+
|------|---------|
|
|
368
|
+
| `0` | No critical findings / no drift |
|
|
369
|
+
| `1` | Drift detected (`drift` command) |
|
|
370
|
+
| `2` | At least one critical fix (`diagnose` command) |
|
|
371
|
+
|
|
372
|
+
---
|
|
373
|
+
|
|
374
|
+
## Automated remediation (`sudiviz fix`)
|
|
375
|
+
|
|
376
|
+
sudiviz can **automatically fix** diagnosed issues — not just report them.
|
|
377
|
+
|
|
378
|
+
### Usage
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
sudiviz fix # List all fixes (dry-run)
|
|
382
|
+
sudiviz fix 1 # Show fix #1 only
|
|
383
|
+
sudiviz fix 1 --apply # Apply fix #1
|
|
384
|
+
sudiviz fix 1,3 --apply # Apply fixes #1 and #3
|
|
385
|
+
sudiviz fix 1-3 --apply # Apply fixes #1, #2, and #3
|
|
386
|
+
sudiviz fix --apply # Apply all fixes
|
|
387
|
+
sudiviz fix --apply --force # Apply all fixes including destructive ones
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Example output
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
$ sudiviz fix
|
|
394
|
+
|
|
395
|
+
Proposed fixes (dry-run):
|
|
396
|
+
|
|
397
|
+
1. CRITICAL Security group missing port 80 from ALB SG
|
|
398
|
+
Add inbound rule to sg-instance: allow TCP/80 from sg-alb
|
|
399
|
+
|
|
400
|
+
aws ec2 authorize-security-group-ingress \
|
|
401
|
+
--region us-east-1 \
|
|
402
|
+
--group-id sg-instance \
|
|
403
|
+
--protocol tcp \
|
|
404
|
+
--port 80 \
|
|
405
|
+
--source-group sg-alb
|
|
406
|
+
|
|
407
|
+
2. WARNING S3 bucket 'my-bucket': server-side encryption not enabled
|
|
408
|
+
Enable SSE-S3 encryption on bucket: my-bucket
|
|
409
|
+
|
|
410
|
+
aws s3api put-bucket-encryption \
|
|
411
|
+
--bucket my-bucket \
|
|
412
|
+
--server-side-encryption-configuration ...
|
|
413
|
+
|
|
414
|
+
Run with --apply to execute these fixes.
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Supported auto-fixes
|
|
418
|
+
|
|
419
|
+
| Issue | Fix applied |
|
|
420
|
+
|-------|-------------|
|
|
421
|
+
| Security group missing port from ALB SG | `ec2:AuthorizeSecurityGroupIngress` |
|
|
422
|
+
| S3 public access not blocked | `s3:PutPublicAccessBlock` |
|
|
423
|
+
| S3 encryption not enabled | `s3:PutBucketEncryption` |
|
|
424
|
+
| RDS publicly accessible | `rds:ModifyDBInstance` |
|
|
425
|
+
| Orphan target group | `elbv2:DeleteTargetGroup` (requires `--force`) |
|
|
426
|
+
| Unused security group | `ec2:DeleteSecurityGroup` (requires `--force`) |
|
|
427
|
+
|
|
428
|
+
### IAM permissions required
|
|
429
|
+
|
|
430
|
+
For `sudiviz diagnose` (read-only):
|
|
431
|
+
- `ReadOnlyAccess` (AWS managed policy)
|
|
432
|
+
|
|
433
|
+
For `sudiviz fix --apply` (write operations):
|
|
434
|
+
- `AmazonEC2FullAccess` — security group fixes
|
|
435
|
+
- `ElasticLoadBalancingFullAccess` — delete orphan target groups
|
|
436
|
+
- `AmazonS3FullAccess` — S3 encryption and public access fixes
|
|
437
|
+
- `AmazonRDSFullAccess` — RDS public accessibility fixes
|
|
438
|
+
|
|
439
|
+
### Safety
|
|
440
|
+
|
|
441
|
+
- **Dry-run by default** — always shows what would change before applying
|
|
442
|
+
- **Destructive operations require `--force`** — delete operations won't run without explicit flag
|
|
443
|
+
- **Selective application** — apply specific fixes by number instead of all at once
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## Continuous monitoring
|
|
448
|
+
|
|
449
|
+
```bash
|
|
450
|
+
sudiviz watch --interval 30 --region us-east-1
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
Re-runs full discovery + analysis every `--interval` seconds. Pair with
|
|
454
|
+
`tmux` for an always-on dashboard. The web mode (`sudiviz graph --output web`)
|
|
455
|
+
is more ergonomic for long-running monitoring — it auto-refreshes via
|
|
456
|
+
WebSocket and lets you inspect nodes interactively.
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## Bonus features
|
|
461
|
+
|
|
462
|
+
- **`sudiviz compare --baseline graph.json`** — diff a saved snapshot vs live topology (shows added/removed nodes).
|
|
463
|
+
- **`sudiviz share --upload`** — push graph JSON to transfer.sh for an ephemeral public link.
|
|
464
|
+
- **`sudiviz diagnose --speak`** — macOS `say` reads the top fixes aloud.
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## Architecture
|
|
469
|
+
|
|
470
|
+
```
|
|
471
|
+
sudiviz/
|
|
472
|
+
├── cli.py # Typer commands: diagnose, drift, graph, tui, watch, compare, share
|
|
473
|
+
├── tui.py # Textual TUI — live table + details pane
|
|
474
|
+
├── web.py # FastAPI + WebSocket broadcast loop
|
|
475
|
+
├── discovery/
|
|
476
|
+
│ ├── aws.py # boto3 + asyncio.to_thread — ECS/EKS/RDS/Lambda/S3/ALB/EC2/SG
|
|
477
|
+
│ ├── terraform.py # `terraform show -json` parser + drift detection
|
|
478
|
+
│ └── models.py # Pydantic v2 — provider-agnostic data models
|
|
479
|
+
├── graph/
|
|
480
|
+
│ ├── builder.py # NetworkX DiGraph construction
|
|
481
|
+
│ ├── analyzer.py # Orphan detection + diagnostic rules + fix suggestions
|
|
482
|
+
│ └── visualizer.py # Terminal (Rich) / Cytoscape JSON / PNG (Graphviz)
|
|
483
|
+
├── web_templates/
|
|
484
|
+
│ ├── index.html # Cytoscape.js app + WebSocket client
|
|
485
|
+
│ ├── style.css # Dark topbar, health-state colours, orphan pulse animation
|
|
486
|
+
│ └── cytoscape.js # Bundled Cytoscape (offline fallback)
|
|
487
|
+
└── utils/
|
|
488
|
+
├── auth.py # boto3 session + STS identity + AWS Console URL builder
|
|
489
|
+
├── reachability.py # VPC Reachability Analyzer integration (opt-in, paid)
|
|
490
|
+
└── branding.py # Logo, version, shared colour palette
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
**Multi-cloud ready.** All discovery returns Pydantic types defined in
|
|
494
|
+
`discovery/models.py`. AWS-specific code is isolated to `discovery/aws.py`.
|
|
495
|
+
Add `discovery/azure.py` or `discovery/gcp.py` to extend without touching
|
|
496
|
+
the graph or visualization layer.
|
|
497
|
+
|
|
498
|
+
---
|
|
499
|
+
|
|
500
|
+
## Performance
|
|
501
|
+
|
|
502
|
+
- All boto3 calls run via `asyncio.to_thread` — parallel discovery with no extra dependencies.
|
|
503
|
+
- New service discovery (ECS, EKS, RDS, Lambda, S3) runs in the **same parallel gather** as ALB/EC2 — no extra latency.
|
|
504
|
+
- If any individual service fails (e.g. no EKS in the account), it logs a warning and returns an empty list — the rest of discovery continues.
|
|
505
|
+
- botocore retries are configured `mode="adaptive"` (exponential backoff + jitter).
|
|
506
|
+
- Pagination is fully drained for every API.
|
|
507
|
+
- The web server caches the latest discovery — multiple browser tabs don't trigger multiple sweeps.
|
|
508
|
+
|
|
509
|
+
---
|
|
510
|
+
|
|
511
|
+
## Development
|
|
512
|
+
|
|
513
|
+
```bash
|
|
514
|
+
git clone https://github.com/sudiptoghosh/sudiviz
|
|
515
|
+
cd sudiviz
|
|
516
|
+
pip install -e '.[dev,all]'
|
|
517
|
+
pytest
|
|
518
|
+
ruff check .
|
|
519
|
+
mypy sudiviz
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
---
|
|
523
|
+
|
|
524
|
+
## Publishing to PyPI
|
|
525
|
+
|
|
526
|
+
```bash
|
|
527
|
+
pip install build twine
|
|
528
|
+
python -m build
|
|
529
|
+
python -m twine upload dist/*
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
After upload, anyone can install with:
|
|
533
|
+
|
|
534
|
+
```bash
|
|
535
|
+
pip install sudiviz
|
|
536
|
+
pip install 'sudiviz[all]'
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## License
|
|
542
|
+
|
|
543
|
+
MIT — see [LICENSE](LICENSE).
|